Share via


Keine E/A-Vorgänge

Ein Dateisystem muss Vorgänge verarbeiten, die in der Regel die direkte Bearbeitung von Benutzerpuffern beinhalten. Solche Vorgänge sind grundsätzlich riskant, da die Benutzeradresse möglicherweise ungültig ist. Dateisysteme müssen sich solchen Vorgängen besonders bewusst sein und sicherstellen, dass sie angemessen geschützt werden. Die folgenden Vorgänge basieren auf dem Flags-Member des Geräteobjekts des Dateisystems, um anzugeben, wie der E/A-Manager Daten zwischen Benutzer und Kerneladressraum übertragen soll:

In der Regel wählt ein Dateisystem weder implizit E/A aus, indem im Flags-Member des erstellten Volumegerätobjekts weder DO_DIRECT_IO noch DO_BUFFERED_IO festgelegt werden.

Der folgende Vorgang ignoriert den Flags-Member des Geräteobjekts des Dateisystems und verwendet keine E/A zum Übertragen von Daten zwischen Benutzer- und Kerneladressraum:

Mit keiner E/A ist das Dateisystem für die Verarbeitung seiner eigenen Datenübertragungsvorgänge verantwortlich. Dadurch kann ein Dateisystem einen Vorgang erfüllen, indem die Daten direkt im Benutzerspeicherpuffer einer Anwendung platziert werden. Das Dateisystem muss daher sicherstellen, dass der Puffer des Benutzers gültig ist, wenn der Vorgang beginnt, und den Puffer ordnungsgemäß behandeln, während der Vorgang ausgeführt wird. Schnelle E/A-Vorgänge übergeben auch unformatierte Zeiger. Entwickler sollten sich bewusst sein, dass die Überprüfung der Gültigkeit des Puffers zu Beginn des Vorgangs nicht ausreicht, um sicherzustellen, dass er während des gesamten Vorgangs gültig bleibt. Beispielsweise könnte eine böswillige Anwendung einen Speicherblock (z. B. über einen Abschnitt) zuordnen, einen E/A-Vorgang ausgeben und die Zuordnung des Speicherblocks aufheben, während der E/A-Vorgang ausgeführt wird.

Es gibt mehrere Möglichkeiten für ein Dateisystem, diese Situation zu behandeln. Ein Mechanismus besteht darin, den physischen Speicher zu sperren, der der Adresse des Benutzers entspricht, und eine zweite Zuordnung im Adressraum des Betriebssystems zu erstellen. Dadurch wird sichergestellt, dass das Dateisystem eine virtuelle Adresse verwendet, die es steuert. Selbst wenn die Benutzeradresse ungültig wird, bleibt die vom Dateisystem erstellte Adresse also gültig. Der FASTFAT-Dateisystemcode verwendet zwei verschiedene Funktionen, um dies zu erreichen. Die erste Funktion sperrt den Puffer des Benutzers:

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 );
}

Diese Routine stellt sicher, dass der physische Speicher, der die Adresse eines Benutzers sichert, während der Vorgang ausgeführt wird, nicht für andere Zwecke wiederverwendet wird. Ein Dateisystem kann dies tun, um den E/A-Vorgang an die zugrunde liegende Volumeverwaltungs- oder Datenträgerklassenebene zu senden, um eine nicht zwischengespeicherte Benutzer-E/A zu erfüllen. In diesem Fall benötigt das Dateisystem keine eigene virtuelle Adresse für den Puffer. Eine zweite Funktion erstellt die Zuordnung des Dateisystems in den Kerneladressraum:

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;
    }
}

Die FASTFAT-Implementierung ermöglicht es der zweiten Routine, auch die Adresse auf Benutzerebene zurückzugeben, was erfordert, dass das FAT-Dateisystem sicherstellt, dass die zurückgegebene Adresse (Benutzer oder Kernel) gültig sein muss. Dazu werden die Schlüsselwörter __try und __except verwendet, um einen geschützten Block zu erstellen.

Diese Routinen befinden sich in der Quelldatei deviosup.c aus den fastfat-Beispielen, die das WDK enthält.

Ein weiteres kritisches Problem tritt auf, wenn die Anforderung im Kontext des Aufrufers nicht erfüllt wird. Wenn ein Dateisystem die Anforderung an einen Workerthread sendet, muss der Treiber den Puffer mit einer MDL sperren, um den Überblick nicht zu verlieren. Die FatPrePostIrp-Funktion in der Quelldatei workque.c aus den fastfat-Beispielen enthält ein Beispiel dafür, wie dieses Problem vom FASTFAT-Dateisystem behandelt wird.

Das FASTFAT-Dateisystem schützt mit diesen Routinen vor einer Vielzahl von Ausfällen, nicht einfach vor ungültigen Benutzerpuffern. Dies ist zwar eine sehr leistungsfähige Technik, aber es geht auch darum, sicherzustellen, dass alle geschützten Codeblöcke alle Ressourcen, die sie möglicherweise enthalten, ordnungsgemäß freigeben. Die freizugebenden Ressourcen umfassen Arbeitsspeicher, Synchronisierungsobjekte oder eine andere Ressource des Dateisystems selbst. Wenn dies versäumt wird, könnte ein anstöniger Angreifer Ressourcenmangel verursachen, indem er viele wiederholte Aufrufe an das Betriebssystem durchführt, um die Ressource zu erschöpfen.