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 파일 시스템은 이러한 루틴을 사용하여 단순히 잘못된 사용자 버퍼가 아닌 광범위한 오류로부터 보호합니다. 이는 매우 강력한 기술이지만 보호된 모든 코드 블록이 보유할 수 있는 리소스를 제대로 해제하도록 하는 것도 포함됩니다. 해제할 리소스에는 메모리, 동기화 개체 또는 파일 시스템 자체의 다른 리소스가 포함됩니다. 이렇게 하지 않으면 운영 체제에서 리소스를 소모하기 위해 반복적인 호출을 많이 수행하여 공격자가 리소스가 부족해질 수 있습니다.