I/O のどちらでもない操作
ファイル システムは通常、ユーザー バッファーを直接操作する操作を処理する必要があります。 このような操作は、ユーザー アドレスが有効でない可能性があるため、本質的に危険です。 ファイル システムでは、このような操作を特に意識し、適切に保護する必要があります。 次の操作は、 ファイル システムのデバイス オブジェクトの「フラグ」メンバーに依存して、I/O マネージャーがユーザーとカーネルのアドレス空間の間でデータを転送する方法を指定します。
通常、ファイル システムは、作成するボリューム デバイス オブジェクトの「フラグ」メンバーに DO_DIRECT_IOもDO_BUFFERED_IO も設定せず、I/O を暗黙的に選択しません。
次の操作は、ファイル システムのデバイス オブジェクトの「フラグ」メンバーを無視し、どちらの I/O も使用してユーザーとカーネルのアドレス空間の間でデータを転送しません。
どちらの I/O も使用していない場合、ファイル システムは独自のデータ転送操作を処理します。 これにより、ファイル システムは、アプリケーションのユーザー空間バッファーにデータを直接配置することで、操作を満たすことができます。 したがって、ファイル システムは、操作の開始時にユーザーのバッファーが有効であることを確認し、操作の進行中に無効になるバッファーを適切に処理する必要があります。 高速 I/O では、生ポインターも渡されます。 開発者は、操作の開始時にバッファーの有効性をチェックするだけでは、操作の間中でバッファーが有効であることを確認するには十分ではないことに注意する必要があります。 たとえば、悪意のあるアプリケーションでは、(たとえばセクションを通じて) メモリ ブロックをマップし、I/O 操作を発行し、I/O 操作の実行中にメモリ ブロックのマップを解除することができます。
ファイル システムでこの状況を処理するには、いくつかの方法があります。 1 つのメカニズムは、ユーザーのアドレスに対応する物理メモリをロックダウンし、オペレーティング システムのアドレス空間に 2 つ目のマッピングを作成することです。 これにより、ファイル システムが制御する仮想アドレスが使用されるようになります。 そのため、ユーザー アドレスが無効になった場合でも、ファイル システムによって作成されたアドレスは有効なままになります。 FASTFAT ファイル システム コードは、これを実現するために 2 つの異なる関数を使用します。 最初の関数は、ユーザーのバッファーをロックダウンします。
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 操作を送信するためにこれを行う場合があります。 このような場合、ファイル システムはバッファーに対する独自の仮想アドレスを必要としません。 2 つ目の関数は、カーネル アドレス空間へのファイル システムのマッピングを作成します。
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 の実装では、2 番目のルーチンもユーザー レベルのアドレスを返すことができます。この場合、FAT ファイル システムでは、返されるアドレス (ユーザーまたはカーネル) が有効である必要があります。 これを行うには、 __try と __except キーワードを使用して保護されたブロックを作成します。
これらのルーチンは、WDK に含まれる fastfat サンプルの deviosup.c ソース ファイルにあります。
もう 1 つの重大な問題は、呼び出し元のコンテキストで要求が満たされていない場合に発生します。 ファイル システムがワーカー スレッドに要求をポストする場合、ドライバーは MDL を使用してバッファーをロックダウンして、その要求を追跡しないようにする必要があります。 fastfat サンプルの workque.c ソース ファイルの FatPrePostIrp 関数は、FASTFAT ファイル システムでこの問題がどのように処理されるかの例を示しています。
FASTFAT ファイル システムは、これらのルーチンを使用して、単に無効なユーザー バッファーではなく、さまざまな障害から保護します。 これは非常に強力な手法ですが、保護されているすべてのコード ブロックが保持している可能性のあるリソースを適切に解放することも含まれます。 解放するリソースには、メモリ、同期オブジェクト、またはファイル システム自体の他のリソースが含まれます。 これを行わないと、攻撃者はオペレーティング システムに何度も繰り返し呼び出しを行いリソースを使い果たすことで、リソースの枯渇を引き起こす可能性があります。