Buffers Kernel, MDL et méthodes d'E/S dans un driver WDM

Ce cours dédiée au développement de drivers en kernel mode sans Framework

Moderator: Rick

Post Reply
Hydraxx
Site Admin
Posts: 144
Joined: Mon Jan 12, 2026 4:04 pm
Location: France
Contact:

Buffers Kernel, MDL et méthodes d'E/S dans un driver WDM

Post by Hydraxx »

Buffers Kernel, MDL et méthodes d'E/S dans un driver WDM

Introduction

Un driver ne peut pas considérer qu'un pointeur provenant d'un processus utilisateur est toujours valide, accessible ou verrouillé en mémoire. Windows fournit donc plusieurs méthodes pour transporter les données entre User Mode et Kernel Mode.

Les trois modèles classiques sont : Buffered I/O, Direct I/O et Neither I/O.

1. Pourquoi Windows ne donne pas toujours directement le pointeur utilisateur

Un pointeur User Mode dépend du contexte du processus courant. La mémoire peut être paginée, libérée, remappée ou devenir invalide entre le moment où la requête est créée et celui où le driver l'utilise.

Le noyau doit donc garantir que le driver travaille avec une représentation sûre du buffer.

2. Buffered I/O

Le driver active cette méthode avec :

Code: Select all

DeviceObject->Flags |= DO_BUFFERED_IO;
Windows crée un buffer système de taille suffisante et effectue les copies nécessaires entre le buffer de l'application et le buffer noyau.

Le driver accède aux données via :

Code: Select all

Irp->AssociatedIrp.SystemBuffer
Pour une écriture :

Code: Select all

PUCHAR input = (PUCHAR)Irp->AssociatedIrp.SystemBuffer;
Pour une lecture, le driver écrit dans le même buffer système, puis l'I/O Manager copie les données vers User Mode à la fin.

Avantages
  • Simple à utiliser.
  • Pas de MDL à manipuler directement.
  • Pratique pour les petits buffers.
Inconvénients
  • Copie supplémentaire.
  • Moins intéressant pour de gros transferts.
3. Direct I/O

Le driver active le mode direct avec :

Code: Select all

DeviceObject->Flags |= DO_DIRECT_IO;
L'I/O Manager décrit alors le buffer utilisateur avec un MDL (Memory Descriptor List).

Le MDL est disponible dans :

Code: Select all

Irp->MdlAddress
Un MDL ne contient pas simplement un pointeur vers les données. Il décrit les pages physiques qui composent le buffer.

4. Qu'est-ce qu'un MDL ?

Un MDL est une structure utilisée par Windows pour décrire une région mémoire potentiellement répartie sur plusieurs pages physiques.

Conceptuellement :

Code: Select all

Buffer virtuel
   |
   v
+---------+---------+---------+
| page A  | page B  | page C  |
+---------+---------+---------+
     |         |         |
     v         v         v
 pages physiques éventuellement non contiguës
API importantes liées aux MDL
  • IoAllocateMdl
  • IoFreeMdl
  • MmProbeAndLockPages
  • MmUnlockPages
  • MmGetSystemAddressForMdlSafe
  • MmGetMdlByteCount
  • MmGetMdlByteOffset
  • MmGetMdlVirtualAddress
5. Mapper un MDL dans l'espace noyau

Pour obtenir une adresse virtuelle utilisable depuis le noyau :

Code: Select all

PVOID buffer = MmGetSystemAddressForMdlSafe(
    Irp->MdlAddress,
    NormalPagePriority
);
Toujours vérifier le résultat :

Code: Select all

if (!buffer)
{
    Irp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES;
    Irp->IoStatus.Information = 0;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);
    return STATUS_INSUFFICIENT_RESOURCES;
}
6. Informations utiles contenues dans un MDL

Code: Select all

ULONG length = MmGetMdlByteCount(Irp->MdlAddress);
ULONG offset = MmGetMdlByteOffset(Irp->MdlAddress);
PVOID va = MmGetMdlVirtualAddress(Irp->MdlAddress);
MmGetMdlVirtualAddress ne doit pas être confondu avec une adresse directement sûre à déréférencer depuis n'importe quel contexte. Pour un accès noyau classique, MmGetSystemAddressForMdlSafe est généralement la routine la plus pratique.

7. Verrouiller soi-même des pages

Dans certains scénarios, le driver construit lui-même un MDL.

Code: Select all

PMDL mdl = IoAllocateMdl(
    userBuffer,
    length,
    FALSE,
    FALSE,
    NULL
);
Puis il verrouille les pages :

Code: Select all

__try
{
    MmProbeAndLockPages(
        mdl,
        UserMode,
        IoReadAccess
    );
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
    IoFreeMdl(mdl);
    return GetExceptionCode();
}
Après utilisation :

Code: Select all

MmUnlockPages(mdl);
IoFreeMdl(mdl);
8. Neither I/O

Si le driver n'utilise ni DO_BUFFERED_IO ni DO_DIRECT_IO, l'I/O Manager peut laisser le driver travailler avec une adresse User Mode.

Cette méthode est plus dangereuse car l'adresse appartient au processus appelant.

Avant de lire :

Code: Select all

__try
{
    ProbeForRead(userBuffer, length, 1);
    // accès au buffer
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
    status = GetExceptionCode();
}
Avant d'écrire :

Code: Select all

__try
{
    ProbeForWrite(userBuffer, length, 1);
    // écriture dans le buffer
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
    status = GetExceptionCode();
}
Attention

ProbeForRead et ProbeForWrite ne rendent pas un pointeur utilisateur éternellement sûr. Le contexte, l'IRQL et la durée de validité du buffer doivent toujours être pris en compte.

9. Comparatif

Code: Select all

Buffered I/O
  -> copie dans SystemBuffer
  -> simple
  -> adapté aux petits transferts

Direct I/O
  -> MDL
  -> pages verrouillées
  -> adapté aux transferts plus importants

Neither I/O
  -> pointeur utilisateur direct
  -> très peu d'abstraction
  -> plus risqué
10. Exemple Direct I/O

Code: Select all

NTSTATUS DispatchRead(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
    UNREFERENCED_PARAMETER(DeviceObject);

    PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
    ULONG requested = stack->Parameters.Read.Length;

    if (!Irp->MdlAddress)
    {
        Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
        Irp->IoStatus.Information = 0;
        IoCompleteRequest(Irp, IO_NO_INCREMENT);
        return STATUS_INVALID_PARAMETER;
    }

    PUCHAR buffer = (PUCHAR)MmGetSystemAddressForMdlSafe(
        Irp->MdlAddress,
        NormalPagePriority
    );

    if (!buffer)
    {
        Irp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES;
        Irp->IoStatus.Information = 0;
        IoCompleteRequest(Irp, IO_NO_INCREMENT);
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    ULONG mdlLength = MmGetMdlByteCount(Irp->MdlAddress);
    ULONG length = min(requested, mdlLength);

    RtlFillMemory(buffer, length, 0x41);

    Irp->IoStatus.Status = STATUS_SUCCESS;
    Irp->IoStatus.Information = length;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);
    return STATUS_SUCCESS;
}
11. Lien avec le DMA

Les MDL deviennent particulièrement importants avec le DMA. Le périphérique ne travaille pas avec l'adresse virtuelle User Mode : Windows utilise la description des pages pour construire les adresses utilisables par le périphérique.

Le chemin devient :

Code: Select all

Buffer utilisateur
     |
     v
MDL
     |
     v
pages physiques
     |
     v
DMA / scatter-gather
12. Résumé

La compréhension des MDL est essentielle pour les drivers de stockage, réseau, audio, vidéo et plus généralement pour les drivers réalisant de gros transferts de données.

Who is online

Users browsing this forum: No registered users and 1 guest