两种 I/O 操作都不是
文件系统必须处理通常涉及直接操作用户缓冲区的操作。 此类操作本质上是有风险的,因为用户地址可能无效。 文件系统必须特别注意此类操作,并确保它们适当地对其进行保护。 以下操作依赖于文件系统设备对象的 Flags 成员来指定 I/O 管理器如何在用户和内核地址空间之间传输数据:
通常,文件系统不会通过在它创建的卷设备对象的 Flags 成员中设置DO_DIRECT_IO或DO_BUFFERED_IO来隐式选择 I/O。
以下操作忽略文件系统设备对象的 Flags 成员,并且不会使用 I/O 在用户和内核地址空间之间传输数据:
文件系统不使用 I/O,负责处理自己的数据传输操作。 这样,文件系统就可以通过将数据直接放入应用程序的用户空间缓冲区来满足操作。 因此,文件系统必须确保操作开始时用户的缓冲区有效,并在操作进行时正常处理缓冲区变得无效。 快速 I/O 还会传递原始指针。 开发人员应注意,在操作开始时检查缓冲区的有效性不足以确保缓冲区在整个操作过程中保持有效。 例如,恶意应用程序可以通过节(例如) )映射内存块 (,发出 I/O 操作,并在 I/O 操作正在进行时取消映射内存块。
文件系统可通过多种方式处理这种情况。 一种机制是锁定与用户地址对应的物理内存,并在操作系统的地址空间中创建第二个映射。 这可确保文件系统使用其控制的虚拟地址。 因此,即使用户地址无效,文件系统创建的地址仍将有效。 FASTFAT 文件系统代码使用两个不同的函数来实现此目的。 第一个函数锁定用户的缓冲区:
VOID
FatLockUserBuffer (
IN PIRP_CONTEXT IrpContext,
IN OUT PIRP Irp,
IN LOCK_OPERATION Operation,
IN ULONG BufferLength
)
/*++
Routine Description:
This routine locks the specified buffer for the specified type of
access. The file system requires this routine because it does not
ask the I/O system to lock its buffers for direct I/O. This routine
can only be called from the file system driver (FSD) while still in the user context.
Note that this is the *input/output* buffer.
Arguments:
Irp - Pointer to the Irp for which the buffer will be locked.
Operation - IoWriteAccess for read operations, or IoReadAccess for
write operations.
BufferLength - Length of user buffer.
Return Value:
None
--*/
{
PMDL Mdl = NULL;
if (Irp->MdlAddress == NULL) {
//
// Allocate the Mdl and Raise if the allocation fails.
//
Mdl = IoAllocateMdl( Irp->UserBuffer, BufferLength, FALSE, FALSE, Irp );
if (Mdl == NULL) {
FatRaiseStatus( IrpContext, STATUS_INSUFFICIENT_RESOURCES );
}
//
// now probe the buffer described by the Irp. If there is an exception,
// deallocate the Mdl and return the appropriate "expected" status.
//
try {
MmProbeAndLockPages( Mdl,
Irp->RequestorMode,
Operation );
} except(EXCEPTION_EXECUTE_HANDLER) {
NTSTATUS Status;
Status = GetExceptionCode();
IoFreeMdl( Mdl );
Irp->MdlAddress = NULL;
FatRaiseStatus( IrpContext,
FsRtlIsNtstatusExpected(Status) ? Status : STATUS_INVALID_USER_BUFFER );
}
}
UNREFERENCED_PARAMETER( IrpContext );
}
此例程可确保在操作正在进行时,不会将支持用户地址的物理内存重用为任何其他目的。 文件系统可能会执行此操作,以便将 I/O 操作发送到基础卷管理层或磁盘类层,以满足非缓存用户 I/O。 在这种情况下,文件系统不需要自己的虚拟地址到缓冲区。 另一个函数创建文件系统到内核地址空间的映射:
PVOID
FatMapUserBuffer (
IN PIRP_CONTEXT IrpContext,
IN OUT PIRP Irp
)
/*++
Routine Description:
This routine conditionally maps the user buffer for the current I/O
request in the specified mode. If the buffer is already mapped, it
just returns its address.
Note that this is the *input/output* buffer.
Arguments:
Irp - Pointer to the Irp for the request.
Return Value:
Mapped address
--*/
{
UNREFERENCED_PARAMETER( IrpContext );
//
// If there is no Mdl, then we must be in the FSD, and can simply
// return the UserBuffer field from the Irp.
//
if (Irp->MdlAddress == NULL) {
return Irp->UserBuffer;
} else {
PVOID Address = MmGetSystemAddressForMdlSafe( Irp->MdlAddress, NormalPagePriority );
if (Address == NULL) {
ExRaiseStatus( STATUS_INSUFFICIENT_RESOURCES );
}
return Address;
}
}
FASTFAT 实现还允许第二个例程返回用户级地址,这要求 FAT 文件系统确保 (用户或内核) 返回的地址必须有效。 它通过使用 __try 和 __except 关键字来创建受保护的块来执行此操作。
这些例程位于 WDK 包含的 fastfat 示例中的 deviosup.c 源文件中。
在调用方上下文中不满足请求时,会出现另一个关键问题。 如果文件系统将请求发布到工作线程,驱动程序必须使用 MDL 锁定缓冲区,才能跟踪它。 fastfat 示例中 workque.c 源文件中的 FatPrePostIrp 函数提供了 FASTFAT 文件系统如何处理此问题的示例。
FASTFAT 文件系统使用这些例程可以防止各种故障,而不仅仅是无效的用户缓冲区。 虽然这是一种非常强大的技术,但它还涉及确保所有受保护的代码块正确释放它们可能持有的任何资源。 要释放的资源包括内存、同步对象或文件系统本身的某些其他资源。 如果这样做失败,可能攻击者可以通过对操作系统进行多次重复调用来耗尽资源,从而导致资源枯竭。