直接 I/O 中的錯誤

最常見的直接 I/O 問題無法正確處理長度為零的緩衝區。 因為 I/O 管理員不會針對零長度傳輸建立 MDL,因此零長度緩衝區會產生 Irp-MdlAddress> 的 NULL 值。

為了對應位址空間,驅動程式應該使用 MmGetSystemAddressForMdlSafe,如果對應失敗,則會傳回 NULL,就像驅動程式通過 NULL MdlAddress 時一樣。 驅動程序應該一律先檢查 NULL 傳回,再嘗試使用傳回的位址。

直接 I/O 牽涉到將使用者的位址空間雙重對應至系統地址緩衝區,讓兩個不同的虛擬位址具有相同的實體位址。 雙重對應會產生下列結果,有時可能會導致驅動程式發生問題:

  • 使用者位址虛擬頁面的位移會變成系統頁面的位移。

    根據對應的頁面粒度,超過這些系統緩衝區結尾的存取可能會長時間被忽視。 除非呼叫端的緩衝區配置在頁面結尾附近,否則寫入緩衝區結尾以外的數據仍會出現在緩衝區中,而且呼叫端不會察覺發生任何錯誤。 如果緩衝區的結尾與頁面結尾重合,則結束以外的系統虛擬位址可能會指向任何專案或可能無效。 這些問題極難找到。

  • 如果呼叫進程有另一個線程修改使用者的記憶體對應,則當使用者的記憶體對應變更時,系統緩衝區的內容將會變更。

    在此情況下,使用系統緩衝區儲存臨時數據可能會導致問題。 來自相同記憶體位置的兩個擷取可能會產生不同的值。

    下列代碼段會在直接 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 所提供的系統緩衝區。