Partager via


Aucune des opérations d’E/S

Un système de fichiers doit gérer les opérations qui impliquent généralement la manipulation directe des mémoires tampons utilisateur. Ces opérations sont intrinsèquement risquées, car l’adresse utilisateur peut ne pas être valide. Les systèmes de fichiers doivent être particulièrement conscients de ces opérations et s’assurer qu’ils les protègent de manière appropriée. Les opérations suivantes s’appuient sur le membre Flags de l’objet d’appareil du système de fichiers pour spécifier comment le gestionnaire d’E/S doit transférer des données entre l’utilisateur et l’espace d’adressage du noyau :

En règle générale, un système de fichiers ne choisit ni E/S implicitement en définissant ni DO_DIRECT_IO ni DO_BUFFERED_IO dans le membre Flags de l’objet de périphérique de volume qu’il crée.

L’opération suivante ignore le membre Flags de l’objet d’appareil du système de fichiers et n’utilise aucune E/S pour transférer des données entre l’utilisateur et l’espace d’adressage du noyau :

À l’aide de l’une ou l’autre des E/S, le système de fichiers est responsable de la gestion de ses propres opérations de transfert de données. Cela permet à un système de fichiers de satisfaire une opération en plaçant directement les données dans la mémoire tampon d’espace utilisateur d’une application. Le système de fichiers doit donc s’assurer que la mémoire tampon de l’utilisateur est valide lorsque l’opération commence et gérer correctement la mémoire tampon qui devient non valide pendant que l’opération est en cours. Les E/S rapides passent également des pointeurs bruts. Les développeurs doivent savoir que la vérification de la validité de la mémoire tampon au début de l’opération n’est pas suffisante pour s’assurer qu’elle reste valide tout au long de l’opération. Par exemple, une application malveillante peut mapper un bloc de mémoire (par le biais d’une section, par exemple), émettre une opération d’E/S et annuler le mappage du bloc de mémoire pendant que l’opération d’E/S est en cours.

Il existe plusieurs façons pour un système de fichiers de gérer cette situation. Un mécanisme consiste à verrouiller la mémoire physique qui correspond à l’adresse de l’utilisateur et à créer un deuxième mappage dans l’espace d’adressage du système d’exploitation. Cela garantit que le système de fichiers utilise une adresse virtuelle qu’il contrôle. Ainsi, même si l’adresse de l’utilisateur devient non valide, l’adresse créée par le système de fichiers reste valide. Pour ce faire, le code du système de fichiers FASTFAT utilise deux fonctions différentes. La première fonction verrouille la mémoire tampon de l’utilisateur :

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

Cette routine garantit que la mémoire physique qui sauvegarde l’adresse d’un utilisateur ne sera pas réutilisée à d’autres fins pendant que l’opération est en cours. Un système de fichiers peut effectuer cette opération afin d’envoyer l’opération d’E/S à la couche de gestion des volumes ou de classe de disque sous-jacente pour satisfaire une E/S utilisateur non mise en cache. Dans ce cas, le système de fichiers n’a pas besoin de sa propre adresse virtuelle pour la mémoire tampon. Une deuxième fonction crée le mappage du système de fichiers dans l’espace d’adressage du noyau :

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

L’implémentation FASTFAT permet à la deuxième routine de retourner également l’adresse au niveau de l’utilisateur, ce qui nécessite que le système de fichiers FAT s’assure que l’adresse retournée (utilisateur ou noyau) doit être valide. Pour ce faire, il utilise les mots clés __try et __except pour créer un bloc protégé.

Ces routines se trouvent dans le fichier source deviosup.c des exemples fastfat que le WDK contient.

Un autre problème critique se produit lorsque la demande n’est pas satisfaite dans le contexte de l’appelant. Si un système de fichiers publie la demande sur un thread worker, le pilote doit verrouiller la mémoire tampon avec un MDL pour ne pas perdre la trace de celle-ci. La fonction FatPrePostIrp dans le fichier source workque.c des exemples fastfat fournit un exemple de la façon dont ce problème est géré par le système de fichiers FASTFAT.

Le système de fichiers FASTFAT protège contre un large éventail de défaillances, et pas simplement contre les mémoires tampons utilisateur non valides, en utilisant ces routines. Bien qu’il s’agit d’une technique très puissante, elle implique également de s’assurer que tous les blocs de code protégés libèrent correctement toutes les ressources qu’ils peuvent contenir. Les ressources à libérer incluent la mémoire, les objets de synchronisation ou une autre ressource du système de fichiers lui-même. Si vous ne le faites pas, une personne malveillante peut provoquer une pénurie de ressources en effectuant de nombreux appels répétitifs dans le système d’exploitation pour épuiser la ressource.