From d0f12d766aade0dced9e2372f4866d05930413d8 Mon Sep 17 00:00:00 2001 From: HorusGod Date: Wed, 4 Mar 2026 15:23:40 +0200 Subject: [PATCH 1/2] fix system hang on USB/removable device removal When a USB drive with an ext4 filesystem is removed while the driver has outstanding I/O, the system hangs indefinitely because KeWaitForSingleObject calls block forever waiting for completions that will never arrive from the removed device. This commit fixes the issue with three changes: - block.c: add 30-second timeouts to all KeWaitForSingleObject calls and cancel pending IRPs on timeout instead of waiting forever - pnp.c: set VCB_DEVICE_REMOVED flag early in both PnpRemove and PnpSurpriseRemove so concurrent I/O threads can fail fast - read.c, write.c: check VCB_DEVICE_REMOVED flag before issuing new I/O and return STATUS_NO_SUCH_DEVICE immediately --- Ext4Fsd/block.c | 51 +++++++++++++++++++++++++++++++++++++++---------- Ext4Fsd/pnp.c | 8 ++++++-- Ext4Fsd/read.c | 6 ++++++ Ext4Fsd/write.c | 5 +++++ 4 files changed, 58 insertions(+), 12 deletions(-) diff --git a/Ext4Fsd/block.c b/Ext4Fsd/block.c index e756d66..2891b90 100644 --- a/Ext4Fsd/block.c +++ b/Ext4Fsd/block.c @@ -400,8 +400,15 @@ Ext2ReadWriteBlocks( } if (Ext2CanIWait()) { - KeWaitForSingleObject( &(pContext->Event), - Executive, KernelMode, FALSE, NULL ); + LARGE_INTEGER Timeout; + Timeout.QuadPart = (LONGLONG)-30 * 10 * 1000 * 1000; /* 30 seconds */ + Status = KeWaitForSingleObject( &(pContext->Event), + Executive, KernelMode, FALSE, &Timeout ); + if (Status == STATUS_TIMEOUT) { + /* Device likely removed — unblock and report error */ + MasterIrp->IoStatus.Status = STATUS_IO_TIMEOUT; + MasterIrp->IoStatus.Information = 0; + } KeClearEvent( &(pContext->Event) ); } else { bMasterCompleted = TRUE; @@ -496,15 +503,23 @@ Ext2ReadSync( Status = IoCallDriver(Vcb->TargetDeviceObject, Irp); if (Status == STATUS_PENDING) { - KeWaitForSingleObject( + LARGE_INTEGER Timeout; + Timeout.QuadPart = (LONGLONG)-30 * 10 * 1000 * 1000; /* 30 seconds */ + Status = KeWaitForSingleObject( Event, Suspended, KernelMode, FALSE, - NULL + &Timeout ); - Status = IoStatus.Status; + if (Status == STATUS_TIMEOUT) { + IoCancelIrp(Irp); + KeWaitForSingleObject(Event, Executive, KernelMode, FALSE, NULL); + Status = STATUS_IO_TIMEOUT; + } else { + Status = IoStatus.Status; + } } } __finally { @@ -610,8 +625,16 @@ Ext2DiskIoControl ( Status = IoCallDriver(DeviceObject, Irp); if (Status == STATUS_PENDING) { - KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL); - Status = IoStatus.Status; + LARGE_INTEGER Timeout; + Timeout.QuadPart = (LONGLONG)-30 * 10 * 1000 * 1000; /* 30 seconds */ + Status = KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, &Timeout); + if (Status == STATUS_TIMEOUT) { + IoCancelIrp(Irp); + KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL); + Status = STATUS_IO_TIMEOUT; + } else { + Status = IoStatus.Status; + } } if (OutputBufferSize) { @@ -644,13 +667,21 @@ Ext2DiskShutDown(PEXT2_VCB Vcb) Status = IoCallDriver(Vcb->TargetDeviceObject, Irp); if (Status == STATUS_PENDING) { - KeWaitForSingleObject(&Event, + LARGE_INTEGER Timeout; + Timeout.QuadPart = (LONGLONG)-30 * 10 * 1000 * 1000; /* 30 seconds */ + Status = KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, - NULL); + &Timeout); - Status = IoStatus.Status; + if (Status == STATUS_TIMEOUT) { + IoCancelIrp(Irp); + KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL); + Status = STATUS_IO_TIMEOUT; + } else { + Status = IoStatus.Status; + } } } else { Status = IoStatus.Status; diff --git a/Ext4Fsd/pnp.c b/Ext4Fsd/pnp.c index 08371a5..9382422 100644 --- a/Ext4Fsd/pnp.c +++ b/Ext4Fsd/pnp.c @@ -249,6 +249,9 @@ Ext2PnpRemove ( Status = Ext2LockVcb(Vcb, IrpContext->FileObject); ExReleaseResourceLite(&Vcb->MainResource); + /* Mark device removed early so concurrent I/O threads fail fast */ + SetLongFlag(Vcb->Flags, VCB_DEVICE_REMOVED); + // // Setup the Irp. We'll send it to the lower disk driver. // @@ -282,7 +285,6 @@ Ext2PnpRemove ( /* dismount volume */ bDeleted = Ext2CheckDismount(IrpContext, Vcb, TRUE); - SetLongFlag(Vcb->Flags, VCB_DEVICE_REMOVED); } __finally { @@ -319,6 +321,9 @@ Ext2PnpSurpriseRemove ( ExReleaseResourceLite(&Vcb->MainResource); + /* Mark device removed early so concurrent I/O threads fail fast */ + SetLongFlag(Vcb->Flags, VCB_DEVICE_REMOVED); + // // Setup the Irp. We'll send it to the lower disk driver. // @@ -352,7 +357,6 @@ Ext2PnpSurpriseRemove ( /* dismount volume */ bDeleted = Ext2CheckDismount(IrpContext, Vcb, TRUE); - SetLongFlag(Vcb->Flags, VCB_DEVICE_REMOVED); } __finally { diff --git a/Ext4Fsd/read.c b/Ext4Fsd/read.c index 1cadba7..788415d 100644 --- a/Ext4Fsd/read.c +++ b/Ext4Fsd/read.c @@ -879,6 +879,12 @@ Ext2Read (IN PEXT2_IRP_CONTEXT IrpContext) FileObject = IrpContext->FileObject; + if (IsFlagOn(Vcb->Flags, VCB_DEVICE_REMOVED)) { + Status = STATUS_NO_SUCH_DEVICE; + bCompleteRequest = TRUE; + __leave; + } + if (FlagOn(Vcb->Flags, VCB_VOLUME_LOCKED) && Vcb->LockFile != FileObject ) { Status = STATUS_ACCESS_DENIED; diff --git a/Ext4Fsd/write.c b/Ext4Fsd/write.c index 669563d..9cad131 100644 --- a/Ext4Fsd/write.c +++ b/Ext4Fsd/write.c @@ -1359,6 +1359,11 @@ Ext2Write (IN PEXT2_IRP_CONTEXT IrpContext) __leave; } + if (IsFlagOn(Vcb->Flags, VCB_DEVICE_REMOVED)) { + Status = STATUS_NO_SUCH_DEVICE; + __leave; + } + if (FlagOn(Vcb->Flags, VCB_VOLUME_LOCKED) && Vcb->LockFile != FileObject ) { Status = STATUS_ACCESS_DENIED; From 5c01ce9f00c273a56c6aa0ee7639f3ed1c3f3246 Mon Sep 17 00:00:00 2001 From: HorusGod Date: Thu, 5 Mar 2026 00:59:24 +0200 Subject: [PATCH 2/2] add timeouts to remaining infinite waits in pnp.c and fsctl.c The previous commit missed three infinite KeWaitForSingleObject calls: - pnp.c: Ext2PnpRemove and Ext2PnpSurpriseRemove wait forever for the lower driver to complete the PnP IRP after device removal - fsctl.c: Ext2IsMediaWriteProtected waits forever for the write-protect check IRP to complete All three now use 30-second timeouts with IRP cancellation on timeout, matching the pattern used in block.c. --- Ext4Fsd/fsctl.c | 14 +++++++++++--- Ext4Fsd/pnp.c | 35 +++++++++++++++++++++++++++++------ 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/Ext4Fsd/fsctl.c b/Ext4Fsd/fsctl.c index ef5819f..ca2b18d 100644 --- a/Ext4Fsd/fsctl.c +++ b/Ext4Fsd/fsctl.c @@ -2019,14 +2019,22 @@ Ext2IsMediaWriteProtected ( Status = IoCallDriver(TargetDevice, Irp); if (Status == STATUS_PENDING) { + LARGE_INTEGER Timeout; + Timeout.QuadPart = (LONGLONG)-30 * 10 * 1000 * 1000; /* 30 seconds */ - (VOID) KeWaitForSingleObject( &Event, + Status = KeWaitForSingleObject( &Event, Executive, KernelMode, FALSE, - (PLARGE_INTEGER)NULL ); + &Timeout ); - Status = IoStatus.Status; + if (Status == STATUS_TIMEOUT) { + IoCancelIrp(Irp); + KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL); + Status = STATUS_IO_TIMEOUT; + } else { + Status = IoStatus.Status; + } } return (BOOLEAN)(Status == STATUS_MEDIA_WRITE_PROTECTED); diff --git a/Ext4Fsd/pnp.c b/Ext4Fsd/pnp.c index 9382422..fc58057 100644 --- a/Ext4Fsd/pnp.c +++ b/Ext4Fsd/pnp.c @@ -198,11 +198,20 @@ Ext2PnpQueryRemove ( IrpContext->Irp); if (Status == STATUS_PENDING) { - KeWaitForSingleObject( &Event, + LARGE_INTEGER Timeout; + Timeout.QuadPart = (LONGLONG)-30 * 10 * 1000 * 1000; /* 30 seconds */ + + Status = KeWaitForSingleObject( &Event, Executive, KernelMode, FALSE, - NULL ); + &Timeout ); + + if (Status == STATUS_TIMEOUT) { + IoCancelIrp(IrpContext->Irp); + KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL); + } + Status = IrpContext->Irp->IoStatus.Status; } @@ -270,12 +279,19 @@ Ext2PnpRemove ( IrpContext->Irp); if (Status == STATUS_PENDING) { + LARGE_INTEGER Timeout; + Timeout.QuadPart = (LONGLONG)-30 * 10 * 1000 * 1000; /* 30 seconds */ - KeWaitForSingleObject( &Event, + Status = KeWaitForSingleObject( &Event, Executive, KernelMode, FALSE, - NULL ); + &Timeout ); + + if (Status == STATUS_TIMEOUT) { + IoCancelIrp(IrpContext->Irp); + KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL); + } Status = IrpContext->Irp->IoStatus.Status; } @@ -342,12 +358,19 @@ Ext2PnpSurpriseRemove ( IrpContext->Irp); if (Status == STATUS_PENDING) { + LARGE_INTEGER Timeout; + Timeout.QuadPart = (LONGLONG)-30 * 10 * 1000 * 1000; /* 30 seconds */ - KeWaitForSingleObject( &Event, + Status = KeWaitForSingleObject( &Event, Executive, KernelMode, FALSE, - NULL ); + &Timeout ); + + if (Status == STATUS_TIMEOUT) { + IoCancelIrp(IrpContext->Irp); + KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL); + } Status = IrpContext->Irp->IoStatus.Status; }