ダイレクト I/O のエラー
よくある直接 I/O の問題は、長さ 0 のバッファーを正しく処理できない点です。 I/O マネージャーでは長さ 0 の転送用の MDL が作成されないため、長さ 0 のバッファーは Irp->MdlAddress で NULL 値になります。
アドレス空間をマップするには、ドライバーが 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 を呼び出すときに、アクセス メソッド (IoReadAccess、IoWriteAccess、 IoModifyAccess) を指定します。 ドライバーで IoReadAccess が指定されている場合は、後で MmGetSystemAddressForMdl または MmGetSystemAddressForMdlSafe によって使用可能になったシステム バッファーへの書き込みを試みてはなりません。