diff --git a/src/Dmf_BusFilter.c b/src/Dmf_BusFilter.c index 23c2fe8..9c323a4 100644 --- a/src/Dmf_BusFilter.c +++ b/src/Dmf_BusFilter.c @@ -429,14 +429,29 @@ Return Value: if (stack->MajorFunction == IRP_MJ_PNP) { - ntStatus = DMF_BusFilter_DispatchPnp(DeviceObject, Irp, stack->MinorFunction); + // Capture the minor code BEFORE dispatching. For IRP_MN_REMOVE_DEVICE, + // DispatchPnp completes the IRP (invalidating `stack`) and deletes the + // device object (invalidating `extension`). Re-reading stack->MinorFunction + // afterwards is a use-after-free that can steer us into IoReleaseRemoveLock + // on the freed extension. + // + const UCHAR minorFunction = stack->MinorFunction; + + ntStatus = DMF_BusFilter_DispatchPnp(DeviceObject, Irp, minorFunction); // For REMOVE_DEVICE, IoReleaseRemoveLockAndWait inside Relations_RemoveDevice // has already released the lock (and waited for all other holders). A second // release here would corrupt the lock count. // - if (stack->MinorFunction != IRP_MN_REMOVE_DEVICE) + if (minorFunction != IRP_MN_REMOVE_DEVICE) { + // Safe: this is a non-REMOVE minor, so no teardown path ran, and the + // IoAcquireRemoveLock reference taken at function entry is still + // outstanding here. That outstanding reference blocks any concurrent + // IoReleaseRemoveLockAndWait (in Relations_RemoveDevice / DeviceCleanup) + // from completing IoDeleteDevice, so `extension` is guaranteed alive at + // the moment we release. We must not touch `extension` after this call. + // IoReleaseRemoveLock(&extension->RemoveLock, Irp); }