共用方式為


直接 I/O 中的錯誤

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

若要對應位址空間,驅動程式應該使用MmGetSystemAddressForMdlSafe,如果對應失敗,則會傳回 Null,因為驅動程式傳遞NullMdlAddress時會傳回 Null。 驅動程式應該一律先檢查 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時,它會指定 (IoReadAccess、IoWriteAccessIoModifyAccess) 的存取方法。 如果驅動程式指定 IoReadAccess,則稍後不得嘗試寫入 MmGetSystemAddressForMdlMmGetSystemAddressForMdlSafe所提供的系統緩衝區。