La suppression d'un périphérique est l'un des endroits les plus dangereux d'un driver WDM.
Le problème est simple à énoncer : le PnP Manager peut demander la suppression alors que du code du driver est encore en train de travailler.
Une IRP, un timer, un DPC ou un callback peut encore utiliser la device extension au moment où un autre thread tente IoDeleteDevice.
Ce cours explique comment IO_REMOVE_LOCK évite ce type de use-after-free.
1. Le problème de lifetime
Imaginez :
Code: Select all
CPU 0 CPU 1
----- -----
DispatchRead IRP_MN_REMOVE_DEVICE
| |
+--> lance une opération +--> IoDeleteDevice
| |
+--> DPC plus tard DEVICE_EXTENSION libérée
|
+--> ext->RegisterBase
^
|
mémoire détruite
Le bug peut produire :
- BSOD
- corruption mémoire
- double free
- appel de fonction dans une image de driver déchargée
IO_REMOVE_LOCK est un mécanisme de comptage des opérations qui empêchent la suppression finale du device.
Principe :
Code: Select all
Operation A -> Acquire -> compteur +1
Operation B -> Acquire -> compteur +1
Operation A -> Release -> compteur -1
REMOVE_DEVICE -> attente du compteur
Operation B -> Release -> compteur -1
compteur suffisamment bas -> suppression autorisée
3. Stocker le remove lock
Il est généralement placé dans la device extension :
Code: Select all
typedef struct _DEVICE_EXTENSION
{
PDEVICE_OBJECT LowerDeviceObject;
IO_REMOVE_LOCK RemoveLock;
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;
Initialisation typique dans AddDevice :
Code: Select all
IoInitializeRemoveLock(
&ext->RemoveLock,
'LrmR',
0,
0
);
- RemoveLock : adresse de IO_REMOVE_LOCK.
- AllocateTag : tag non nul utile au suivi/debug.
- MaxLockedMinutes : durée maximale attendue pour une acquisition, principalement utile avec Driver Verifier/debug.
- HighWatermark : nombre maximal d'acquisitions attendu, également utile au diagnostic.
5. IoAcquireRemoveLock
Avant de commencer une opération qui doit garder le device vivant :
Code: Select all
NTSTATUS status = IoAcquireRemoveLock(&ext->RemoveLock, Irp);
if (!NT_SUCCESS(status))
{
Irp->IoStatus.Status = status;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return status;
}
Code: Select all
Tag = Irp
Après que la suppression finale a commencé, une nouvelle acquisition peut échouer avec STATUS_DELETE_PENDING.
6. IoReleaseRemoveLock
Quand l'opération ne dépend plus du DEVICE_OBJECT :
Code: Select all
IoReleaseRemoveLock(&ext->RemoveLock, Irp);
7. Le remove lock ne concerne pas seulement les IRP
Il doit aussi protéger toute référence temporaire à du code/état du périphérique :
- timer armé ;
- DPC qui sera exécuté plus tard ;
- work item ;
- callback enregistré auprès d'un autre composant ;
- opération asynchrone interne ;
- IRP créée/envoyée par le driver ;
- attente hardware encore active.
Code: Select all
NTSTATUS status = IoAcquireRemoveLock(&ext->RemoveLock, &ext->Timer);
if (NT_SUCCESS(status))
{
KeSetTimer(&ext->Timer, dueTime, &ext->Dpc);
}
Code: Select all
IoReleaseRemoveLock(&ext->RemoveLock, &ext->Timer);
Pattern :
Code: Select all
NTSTATUS DispatchDeviceControl(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
PDEVICE_EXTENSION ext = DeviceObject->DeviceExtension;
NTSTATUS status = IoAcquireRemoveLock(&ext->RemoveLock, Irp);
if (!NT_SUCCESS(status))
{
Irp->IoStatus.Status = status;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return status;
}
// traitement
IoReleaseRemoveLock(&ext->RemoveLock, Irp);
return status;
}
9. IRP transmises sans completion routine
Si le driver transmet simplement une IRP et ne souhaite pas rester propriétaire après IoCallDriver :
Code: Select all
IoSkipCurrentIrpStackLocation(Irp);
NTSTATUS status = IoCallDriver(ext->LowerDeviceObject, Irp);
IoReleaseRemoveLock(&ext->RemoveLock, Irp);
return status;
10. IRP avec completion routine
Si le driver doit rester impliqué jusqu'à la completion du driver inférieur, le remove lock doit rester acquis pendant toute cette durée.
Pattern :
Code: Select all
NTSTATUS CompletionRoutine(
PDEVICE_OBJECT DeviceObject,
PIRP Irp,
PVOID Context)
{
PDEVICE_EXTENSION ext = (PDEVICE_EXTENSION)Context;
// traitement de completion
IoReleaseRemoveLock(&ext->RemoveLock, Irp);
return STATUS_CONTINUE_COMPLETION;
}
Code: Select all
IoCopyCurrentIrpStackLocationToNext(Irp);
IoSetCompletionRoutine(
Irp,
CompletionRoutine,
ext,
TRUE,
TRUE,
TRUE
);
return IoCallDriver(ext->LowerDeviceObject, Irp);
11. IRP_MN_REMOVE_DEVICE et IoReleaseRemoveLockAndWait
C'est le moment critique.
Le driver acquiert normalement le remove lock au début de DispatchPnP, y compris pour IRP_MN_REMOVE_DEVICE.
Le schéma recommandé est :
- Acquérir le remove lock pour REMOVE_DEVICE.
- Préparer l'arrêt des nouvelles opérations.
- Transmettre l'IRP_MN_REMOVE_DEVICE au driver inférieur.
- Appeler IoReleaseRemoveLockAndWait.
- Attendre ainsi toutes les autres acquisitions encore vivantes.
- Libérer les ressources internes.
- IoDetachDevice.
- IoDeleteDevice.
Code: Select all
case IRP_MN_REMOVE_DEVICE:
{
IoSkipCurrentIrpStackLocation(Irp);
NTSTATUS status = IoCallDriver(ext->LowerDeviceObject, Irp);
IoReleaseRemoveLockAndWait(&ext->RemoveLock, Irp);
IoDetachDevice(ext->LowerDeviceObject);
IoDeleteDevice(DeviceObject);
return status;
}
12. Que fait exactement IoReleaseRemoveLockAndWait ?
Cette routine :
- libère l'acquisition correspondant à son Tag ;
- marque le remove lock comme engagé dans la suppression ;
- attend que toutes les autres acquisitions soient relâchées ;
- empêche de nouvelles opérations de repartir normalement après le début de la suppression.
Il ne faut pas réinitialiser le même remove lock pour « ressusciter » l'objet.
13. Remove lock et PnP state
Le remove lock et l'état PnP résolvent deux problèmes différents :
- PnP state : faut-il accepter, staller ou refuser une opération ?
- Remove lock : existe-t-il encore du code qui doit empêcher la destruction finale ?
Code: Select all
State = SurpriseRemoved
=> refuser une nouvelle lecture hardware
RemoveLock count > 0
=> un DPC / IRP / callback existant n'a pas encore terminé
=> ne pas IoDeleteDevice
Une IRP mise en queue peut garder un remove lock acquis pendant tout son séjour dans la queue.
Quand elle est :
- traitée normalement : release à la fin ;
- annulée : release dans le chemin d'annulation/completion ;
- purge après surprise removal : release pour chaque IRP terminée ;
15. Le problème du driver unload
La destruction d'un DEVICE_OBJECT et le déchargement de l'image du driver sont liés mais distincts.
Un driver peut avoir supprimé un device tout en conservant d'autres devices.
Inversement, l'image du driver ne doit jamais être déchargée tant qu'un callback, timer, DPC ou completion routine peut encore sauter dans son code.
Le principe général reste :
Code: Select all
Retirer les callbacks
Annuler les timers
Vider/attendre les DPC et work items selon le mécanisme
Terminer les IRP
Attendre les remove locks
Détacher
Supprimer les DEVICE_OBJECT
Puis seulement permettre l'unload final
- IO_REMOVE_LOCK : structure de suivi des références empêchant la suppression.
- IoInitializeRemoveLock : initialise le remove lock.
- IoAcquireRemoveLock : prend une référence logique ; peut échouer après DELETE_PENDING.
- IoReleaseRemoveLock : relâche une référence.
- IoReleaseRemoveLockAndWait : utilisé dans REMOVE_DEVICE pour attendre toutes les références restantes.
- IoDetachDevice : détache le DEVICE_OBJECT de la stack.
- IoDeleteDevice : détruit le DEVICE_OBJECT.
- IoSetCompletionRoutine : permet de conserver une implication jusqu'à la completion inférieure.
- Acquérir un remove lock et oublier de le relâcher.
- Relâcher deux fois le même tag/opération.
- Utiliser un tag différent entre Acquire et Release.
- Appeler IoReleaseRemoveLockAndWait sans acquisition préalable.
- Appeler IoReleaseRemoveLockAndWait avant de transmettre REMOVE_DEVICE au driver inférieur.
- Appeler IoDeleteDevice avant que les opérations en cours aient fini.
- Oublier que timers, DPC, callbacks et work items sont aussi des références au code/état du driver.
- Réinitialiser le remove lock après IoReleaseRemoveLockAndWait.
IO_REMOVE_LOCK transforme la question :
Code: Select all
"Est-ce que je peux supprimer ce device ?"
Code: Select all
"Toutes les opérations qui dépendent encore de ce device ont-elles relâché leur référence ?"
