Windows内核驱动卸载触发0x000000ce系统崩溃问题求助
我参考《Windows内核编程》编写了ProcessThreadMonitorDriver.sys驱动,加载后功能正常,但卸载时触发系统崩溃。WinDbg调试得到的错误信息如下:
*** Fatal System Error: 0x000000ce
(0xFFFFF80260A71390,0x0000000000000010,0xFFFFF80260A71390,0x0000000000000000)Driver at fault: ProcessThreadMonitorDriver.sys.
Break instruction exception - code 80000003 (first chance)
nt_exe!DbgBreakPointWithStatus:
调试中偶尔提示无效内存访问,但未明确根因。以下是驱动核心代码片段,请求协助排查卸载崩溃问题:
DriverEntry函数
extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) { UNREFERENCED_PARAMETER(RegistryPath); auto status = STATUS_SUCCESS; InitializeListHead(&g_Globals.ItemsHead); g_Globals.Mutex.Init(); PDEVICE_OBJECT DeviceObject = nullptr; UNICODE_STRING symLink = RTL_CONSTANT_STRING(L"\\??\\sysmon"); bool symLinkCreated = false; do { UNICODE_STRING devName = RTL_CONSTANT_STRING(L"\\Device\\sysmon"); status = IoCreateDevice(DriverObject, 0, &devName, FILE_DEVICE_UNKNOWN, 0, TRUE, &DeviceObject); if (!NT_SUCCESS(status)) { KdPrint((DRIVER_PREFIX "failed to create device (0x%08X)\n", status)); break; } DeviceObject->Flags |= DO_DIRECT_IO; status = IoCreateSymbolicLink(&symLink, &devName); if (!NT_SUCCESS(status)) { KdPrint((DRIVER_PREFIX "failed to create sym link (0x%08X)\n", status)); break; } symLinkCreated = true; // register for process notifications status = PsSetCreateProcessNotifyRoutineEx(OnProcessNotify, FALSE); if (!NT_SUCCESS(status)) { KdPrint((DRIVER_PREFIX "failed to register process callback (0x%08X)\n", status)); break; } status = PsSetCreateThreadNotifyRoutine(OnThreadNotify); if (!NT_SUCCESS(status)) { KdPrint((DRIVER_PREFIX "failed to set thread callbacks (status=%08X)\n", status)\ ); break; } } while (false); if (!NT_SUCCESS(status)) { if (symLinkCreated) IoDeleteSymbolicLink(&symLink); if (DeviceObject) IoDeleteDevice(DeviceObject); } DriverObject->DriverUnload = SysMonUnload; DriverObject->MajorFunction[IRP_MJ_CREATE] = DriverObject->MajorFunction[IRP_MJ_CLOSE] = SysMonCreateClose; DriverObject->MajorFunction[IRP_MJ_READ] = SysMonRead; return status; }
SysMonCreateClose函数
NTSTATUS SysMonCreateClose(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp) { UNREFERENCED_PARAMETER(DeviceObject); Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_SUCCESS; }
SysMonUnload函数
void SysMonUnload(PDRIVER_OBJECT DriverObject) { // unregister process notifications PsSetCreateProcessNotifyRoutineEx(OnProcessNotify, TRUE); UNICODE_STRING symLink = RTL_CONSTANT_STRING(L"\\??\\sysmon"); IoDeleteSymbolicLink(&symLink); IoDeleteDevice(DriverObject->DeviceObject); // free remaining items while (!IsListEmpty(&g_Globals.ItemsHead)) { auto entry = RemoveHeadList(&g_Globals.ItemsHead); ExFreePool(CONTAINING_RECORD(entry, FullItem<ItemHeader>, Entry)); } }
SysMonRead函数
NTSTATUS SysMonRead(PDEVICE_OBJECT, PIRP Irp) { UNREFERENCED_PARAMETER(Irp); auto stack = IoGetCurrentIrpStackLocation(Irp); auto len = stack->Parameters.Read.Length; auto status = STATUS_SUCCESS; auto count = 0; NT_ASSERT(Irp->MdlAddress); // we're using Direct I/O auto buffer = (UCHAR*)MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority); if (!buffer) { status = STATUS_INSUFFICIENT_RESOURCES; } else { AutoLock lock(g_Globals.Mutex); while (true) { if (IsListEmpty(&g_Globals.ItemsHead)) // can also check g_Globals.ItemCount break; auto entry = RemoveHeadList(&g_Globals.ItemsHead); auto info = CONTAINING_RECORD(entry, FullItem<ItemHeader>, Entry); auto size = info->Data.Size; if (len < size) { // user's buffer is full, insert item back InsertHeadList(&g_Globals.ItemsHead, entry); break; } g_Globals.ItemCount--; ::memcpy(buffer, &info->Data, size); len -= size; buffer += size; count += size; // free data after copy ExFreePool(info); } } Irp->IoStatus.Status = status; Irp->IoStatus.Information = count; IoCompleteRequest(Irp, 0); return status; }
崩溃根因分析与修复方案
1. 遗漏线程回调注销
SysMonUnload中仅注销了进程回调,未调用PsRemoveCreateThreadNotifyRoutine(OnThreadNotify)注销线程回调。驱动卸载后若有线程事件触发,系统会尝试调用已释放的OnThreadNotify函数,直接导致0xCE类型的无效内存访问崩溃。
2. 资源释放顺序错误
当前卸载流程先注销进程回调,再删除设备和符号链接,最后释放列表项。正确顺序应优先注销所有回调,避免回调在后续资源释放过程中触发;再持有锁释放全局列表资源;最后清理设备和符号链接。
3. 全局列表访问未加锁
SysMonUnload遍历g_Globals.ItemsHead时未持有互斥锁,而SysMonRead是加锁访问的。卸载时若有并发的Read操作,会引发列表结构损坏或内存重复释放问题。
4. 注销回调未检查返回状态
调用PsSetCreateProcessNotifyRoutineEx和PsRemoveCreateThreadNotifyRoutine时未验证返回值,若注销失败(如回调未注册),可能引发后续逻辑异常。
修复后的SysMonUnload函数示例
void SysMonUnload(PDRIVER_OBJECT DriverObject) { // 先注销所有回调,避免后续资源释放时触发回调 NTSTATUS status; status = PsSetCreateProcessNotifyRoutineEx(OnProcessNotify, TRUE); if (!NT_SUCCESS(status)) { KdPrint((DRIVER_PREFIX "failed to unregister process callback (0x%08X)\n", status)); } status = PsRemoveCreateThreadNotifyRoutine(OnThreadNotify); if (!NT_SUCCESS(status)) { KdPrint((DRIVER_PREFIX "failed to unregister thread callback (0x%08X)\n", status)); } // 加锁安全释放全局列表资源 AutoLock lock(g_Globals.Mutex); while (!IsListEmpty(&g_Globals.ItemsHead)) { auto entry = RemoveHeadList(&g_Globals.ItemsHead); auto info = CONTAINING_RECORD(entry, FullItem<ItemHeader>, Entry); ExFreePool(info); g_Globals.ItemCount--; // 同步更新计数 } // 清理设备和符号链接 UNICODE_STRING symLink = RTL_CONSTANT_STRING(L"\\??\\sysmon"); IoDeleteSymbolicLink(&symLink); IoDeleteDevice(DriverObject->DeviceObject); }
内容的提问来源于stack exchange,提问作者Omer Cohen

