ダイレクト I/O のエラー

よくある直接 I/O の問題は、長さ 0 のバッファーを正しく処理できない点です。 I/O マネージャーでは長さ 0 の転送用の MDL が作成されないため、長さ 0 のバッファーは Irp->MdlAddressNULL 値になります。

アドレス空間をマップするには、ドライバーが MmGetSystemAddressForMdlSafe を使用する必要があります。これは、ドライバーがNULL MdlAddressを渡した場合と同様に、マッピングが失敗した場合にNULL を返します。 ドライバーは、返されたアドレスを使用する前に、常に NULL の戻り値をチェックする必要があります。

ダイレクト I/O では、2 つの異なる仮想アドレスが同じ物理アドレスになるように、ユーザーのアドレス空間をシステム アドレス バッファーにダブルマッピングします。 ダブルマッピングでは次の結果が生じ、ドライバーに問題が発生する場合があります。

  • ユーザーのアドレスの仮想ページへのオフセットが、システム ページへのオフセットになります。

    これらのシステム バッファーの末尾を超えるアクセスは、マッピングのページ細分性によっては、長時間気付かない場合があります。 呼び出し元のバッファーがページの末尾付近に割り当てられている場合を除き、バッファーの末尾を超えて書き込まれたデータはバッファーに表示され、呼び出し元はエラーが発生したことを認識しません。 バッファーの末尾がページの末尾と一致する場合、末尾を超えるシステム仮想アドレスが何かを指しているか、無効である可能性があります。 このような問題を見つけることは非常に困難な場合があります。

  • 呼び出し元のプロセスに、ユーザーのメモリ マッピングを変更する別のスレッドがある場合、ユーザーのメモリ マッピングが変更されると、システム バッファーの内容が変更されます。

    このような状況では、システム バッファーを使用してスクラッチ データを格納すると、問題が発生する可能性があります。 同じメモリ位置から 2 回フェッチすると、異なる値が生成される場合があります。

    次のコード スニペットは、直接 I/O 要求で文字列を受け取り、その文字列を大文字に変換しようとします。

    PWCHAR  PortName = NULL;
    
    PortName = (PWCHAR)MmGetSystemAddressForMdlSafe(irp->MdlAddress, NormalPagePriority);
    
    //
    // Null-terminate the PortName so that RtlInitUnicodeString will not
    // be invalid.
    //
    PortName[Size / sizeof(WCHAR) - 1] = UNICODE_NULL;
    
    RtlInitUnicodeString(&AdapterName, PortName);
    

    バッファーの形式が正しくない可能性があるため、コードは最後のバッファー文字として Unicode NULL を強制しようとします。 ただし、基になる物理メモリがユーザー とカーネル モードの両方のアドレスに二重にマップされている場合、この書き込み操作が完了するとすぐに、プロセス内の別のスレッドによってバッファーが上書きされる可能性があります。

    逆に、NULL が存在しない場合、RtlInitUnicodeString の呼び出しがバッファーの範囲を超える可能性があり、システム マッピングの範囲外にある場合はバグ チェックが発生する可能性があります。

ドライバーが独自の MDL を作成してマップする場合は、プローブ対象のメソッドでのみ MDL にアクセスするようにする必要があります。 つまり、ドライバーが MmProbeAndLockPages を呼び出すときに、アクセス メソッド (IoReadAccessIoWriteAccessIoModifyAccess) を指定します。 ドライバーで IoReadAccess が指定されている場合は、後で MmGetSystemAddressForMdl または MmGetSystemAddressForMdlSafe によって使用可能になったシステム バッファーへの書き込みを試みてはなりません。