直接 I/O 出错
最常见的直接 I/O 问题是无法正确处理零长度缓冲区。 由于 I/O 管理器不会为零长度传输创建 MDL,因此零长度缓冲区会在 Irp-MdlAddress> 上生成 NULL 值。
若要映射地址空间,驱动程序应使用 MmGetSystemAddressForMdlSafe,如果映射失败,它将返回 NULL ,就像驱动程序通过 NULLMdlAddress 一样。 在尝试使用返回的地址之前,驱动程序应始终检查 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 的调用可能会超出缓冲区的范围,如果它位于系统映射之外,则可能会导致 bug 检查。
如果驱动程序创建并映射自己的 MDL,它应确保它仅使用已探测的方法访问 MDL。 也就是说,当驱动程序调用 MmProbeAndLockPages 时,它会指定 (IoReadAccess、 IoWriteAccess 或 IoModifyAccess) 的访问方法。 如果驱动程序指定 IoReadAccess,则以后不得尝试写入由 MmGetSystemAddressForMdl 或 MmGetSystemAddressForMdlSafe 提供的系统缓冲区。
反馈
https://aka.ms/ContentUserFeedback。
即将发布:在整个 2024 年,我们将逐步淘汰作为内容反馈机制的“GitHub 问题”,并将其取代为新的反馈系统。 有关详细信息,请参阅:提交和查看相关反馈