WDF driver stuck after uninstalling while app has HANDLE open

We have a WDF based software-only driver that’s very similar to the “NOTHING” example at http://www.osronline.com/article.cfm^article=390.htm
The DriverEntry function is basically identical, and the only real difference in the DeviceAdd callback is that we change some init settings before calling WdfDeviceCreate:

NTSTATUS MyEvtDeviceAdd(WDFDRIVER Driver, PWDFDEVICE_INIT DeviceInit)
{
	DECLARE_CONST_UNICODE_STRING(deviceName, L"\\Device\\" MY_DEVICE_NAME);
	DECLARE_CONST_UNICODE_STRING(dosDeviceName, L"\\DosDevices\\" MY_DEVICE_NAME);

	WdfDeviceInitSetCharacteristics(DeviceInit, FILE_DEVICE_SECURE_OPEN, TRUE);
	WdfDeviceInitSetDeviceType(DeviceInit, FILE_DEVICE_UNKNOWN);

	NTSTATUS status = WdfDeviceInitAssignName(DeviceInit, &deviceName);
	if (!NT_SUCCESS(status)) ...

	status = WdfDeviceInitAssignSDDLString(DeviceInit, &SDDL_DEVOBJ_SYS_ALL_ADM_ALL);
	if (!NT_SUCCESS(status)) ...

	WDF_OBJECT_ATTRIBUTES deviceAttributes;
	WDF_OBJECT_ATTRIBUTES_INIT(&deviceAttributes);
	deviceAttributes.EvtCleanupCallback = MyEvtDeviceCleanup;
	deviceAttributes.EvtDestroyCallback = MyEvtDeviceDestroy;

	WDFDEVICE hDevice = NULL;
	status = WdfDeviceCreate(&DeviceInit, &deviceAttributes, &hDevice);
	if (!NT_SUCCESS(status)) ...

	status = WdfDeviceCreateSymbolicLink(hDevice, &dosDeviceName);
	if (!NT_SUCCESS(status)) ...

	WDF_IO_QUEUE_CONFIG ioQueueConfig;
	WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&ioQueueConfig, WdfIoQueueDispatchSequential);
	ioQueueConfig.EvtIoDeviceControl = DevIoCtrlCallback;

	WDFQUEUE hQueue;
	status = WdfIoQueueCreate(hDevice, &ioQueueConfig, WDF_NO_OBJECT_ATTRIBUTES, &hQueue);
	if (!NT_SUCCESS(status)) ...

	return status;
}

It’s part of a larger package, and the update mechanism for that package currently does a full uninstall of all components followed by an install of the new version.
That means when updating the package, the driver is uninstalled (via SetupDiCallClassInstaller(DIF_REMOVE)) and after that the new driver is installed.
This works nicely as long as no application has an open HANDLE to the driver.
Now AFAIK it’s not possible to have the driver unload while an application still has a HANDLE open. (If I’m mistaken here please let me know, because allowing the driver to unload even if there are open HANDLEs would be the best option for us.)

The problem is: If the last HANDLE is not closed during the few seconds that DIF_REMOVE will wait for just that to happen, the driver will stay loaded. I had expected that after such an “uninstall”, the driver would automatically unload when the last HANDLE to it is closed. Which unfortunately doesn’t happen, the driver stays loaded. At that point the system is in a rather unfortunate state: The device itself is no longer registered (because DIF_REMOVE succeeded - after all it removed the registry keys for the device, and after e.g. a reboot it will be gone), so we cannot retry by running the uninstall process again. Yet the driver is still running, so when the installer tries to install the new version, the newly registered device will not be able to start because of a name collision (device name). Which is … bad :slight_smile:

What I found out is that if I do a “scan for hardware changes” in device manager after the “uninstall without unload”, the driver will then finally unload (given that there are no more open HANDLES at that time of course). The same happens if I start or stop any other device.

First question: Is this a known problem, or is this likely caused by something that our driver does wrong?

Second question: How do we fix this?

The only way I have found so far is to add an IOCTL to our driver which simply makes it call WdfDeviceSetFailed(WdfIoQueueGetDevice(Queue), WdfDeviceFailedNoRestart). We could then send this IOCTL in the retry-loop of our driver-installer. But this seems like a rather ugly solution to me. Triggering whatever happens with every device enable/disable seems a bit cleaner to me – assuming that we can trigger it programmatically. Even better would be a solution where the “uninstalled but not unloaded” driver is automatically unloaded when the last HANDLE is closed.

Regards,
Paul Groke

it’s not possible to have the driver unload while an application still has a HANDLE open

Right. That couldn’t possibly work, right? Your open handle is a handle to a File Object, which has a reference on a Device Object. The Device Object can’t go away until the File Object goes away. And the driver “owns” the Device Object (an over-simplification, but let’s go with it).

I had expected that after such an “uninstall”, the driver would automatically unload when the last HANDLE to it is closed.

Yeah, I don’t THINK that happens.

Yet the driver is still running

No, not really. The Device Object still exists. The Device Object is what has the name, so the name collides when you attempt to create the new Device Object.

How do we fix this?

I have no idea. At this point, can you still “disable” the device in Device Manager?

Peter

@“Peter_Viscarola_(OSR)” said:

it’s not possible to have the driver unload while an application still has a HANDLE open

Right. That couldn’t possibly work, right?

Without knowing too much about the NT internals, it’s easy to imagine how this could work. The HANDLE could simply be “orphaned”. By which I mean: the handle value itself would remain valid (preventing reuse and spurious errors when closing), but the association to the device object could be removed. Of course this can’t work for pending IO requests, but those could be cancelled. And as soon as all IO requests have been completed one way or the other, the device object could then be removed. Any attempt to use such an orphaned handle after that would simply result in an immediate error. (Just like a socket doesn’t necessarily keep alive the connection and/or server application it was once connected to.)

I already assumed that it doesn’t work like that. I still wanted to ask for the hope of being wrong so to say.

Yet the driver is still running

No, not really. The Device Object still exists. The Device Object is what has the name, so the name collides when you attempt to create the new Device Object.

I don’t understand. The Device Object still exists, the driver code is still loaded. It’s registered callbacks (PsSetCreateProcessNotifyRoutine and the like) are still being called, it still does all the things we designed it to do. Even the symbolic link is still there and can still be used to open new handles from usermode applications. This is what I mean by “still running”.

I have no idea. At this point, can you still “disable” the device in Device Manager?

No. If Device Manager was already running before uninstalling the driver, then it will still show the device icon. But that’s about it. Trying to disable it immediately displays the “You must restart your computer” dialog (and the driver still remains loaded/operational). Selecting “Properties” from the context menu opens an empty properties window. If Device Manager is (re)started after the uninstall, then the icon won’t even be shown.

What does setupapi.dev.log say when you try to uninstall the device? Is it going through a query remove -> remove transition or you are just trying to destroy the device node in its running state? You must go through the query remove->remove transition. When you do this, it will fail with an open handle and you should no longer see the device disappear. In addition, the app which has an open handle should register for file handle notifications so that it can participate in the query remove logic, be a nice citizen, and close the handle.

d

@Doron_Holan
setupapi.dev.log didn’t say anything. I probably need to enable logging somewhere. (The install process logs plenty but strangely the uninstall didn’t log anything…) Have to check that tomorrow though I fear that it won’t help. After all we’re using SetupDi and some standard MS class installer - nothing unusual going on there.
Re. the query remove, I do know that the driver receives a pnp query remove IRP when the driver is being uninstalled. All of this is handled by WDF though.

You must go through the query remove->remove transition. When you do this, it will fail with an open handle and you should no longer see the device disappear.

But I want to (need to) uninstall the device. I just also want the actual driver service to stop as soon as possible after that. And ideally a way to check if it’s still running.