Communication between user and kernel-mode

As a follow-up post on Windows Services and Minifilters, Today I’ll explain one way we can send notifications from kernel-mode to an user-mode application.  Let’s remember Windows provides fault tolerance protection in the form of rings, thus an offending code in one of the rings should not compromise the stability of the system. My metadata filesystem project has a minifilter driver that notifies a Windows service when an important event (event I’m interested in) has occurred. 

The basics to do this are:

  1. Minifilter driver creates a device by calling IoCreateDevice and also creates a symbolic link through IoCreateSymbolicLink
    NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) { NTSTATUS retval = STATUS_SUCCESS; UNICODE_STRING DeviceName; UNICODE_STRING LinkName; PDEVICE_OBJECT EventDrvDeviceObject; UNREFERENCED_PARAMETER(DriverObject); UNREFERENCED_PARAMETER(RegistryPath); KdPrint(("MetaFsDriver::DriverEntry - Entered.\n")); g_FilterDriverObject = DriverObject; // Create Dispatch Entry Points. for (int x = 0; x <= IRP_MJ_MAXIMUM_FUNCTION; ++x) DriverObject->MajorFunction[x] = MetaFsDispatchPassThrough; DriverObject->DriverUnload = MetaFsDriverUnload; DriverObject->MajorFunction[IRP_MJ_CREATE] = MetaFsDispatchCreate; DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = MetaFsDispatchDeviceControl; // Create device object RtlInitUnicodeString(&DeviceName, MetaFs_NT_DEVICE_NAME); retval = IoCreateDevice( DriverObject, 0, &DeviceName, FILE_DEVICE_UNKNOWN, 0, FALSE, &EventDrvDeviceObject); if (!NT_SUCCESS(retval)) { return retval; } KdPrint(("MetaFsDriver::DriverEntry - Device Created.\n")); // Create symbolic links RtlInitUnicodeString(&LinkName, MetaFs_WIN32_DEVICE_NAME); retval = IoCreateSymbolicLink(&LinkName, &DeviceName); if (!NT_SUCCESS(retval)) { IoDeleteDevice(EventDrvDeviceObject); return retval; } KdPrint(("MetaFsDriver::DriverEntry - Symbolic link created.\n")); // Set FastIO dispatch table DriverObject->FastIoDispatch = &g_FastIoDispatch; retval = IoRegisterFsRegistrationChange(DriverObject, MetaFsNotificationCallback); KdPrint(("MetaFsDriver::DriverEntry - Exited.\n")); return retval; }

    Since code above is running in kernel mode, it’s a bit tricky to send any output to Visual Studio output window, but fear not we can always make use of KdPrint to send messages to the kernel debugger, at the same time we also need to use DebugView to  capture this messages


    Our minidriver details can be seen via DeviceTree


  2. Once our minifilter driver is running and it’s created a symbolic link, the user mode application  (in our case, a Windows service) creates an event by calling CreateEvent and then uses DeviceIoControl to send the event handle to the driver. In order to send our control code, we use the name of the symbolic link that was created by minifilter driver.

    VOID InitializeCommunicationWithDriver() { HANDLE hFile; DWORD cbSize; if ((hFile = CreateFile(TARGET_DRIVER, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)) != INVALID_HANDLE_VALUE) { if ((g_filterEventHandle = CreateEvent(NULL, FALSE, FALSE, NULL)) != NULL) { if (DeviceIoControl(hFile, IOCTL_REGISTER_EVENT, &g_filterEventHandle, sizeof(g_filterEventHandle), NULL, NULL, &cbSize, NULL)) WriteEvent(EventInformation(EventType::INFORMATION_TYPE, L"Event successfully created....")); } CloseHandle(hFile); } }

  3. The call to CreateEvent creates a kernel mode KEVENT object and makes an entry into the application process’s handle table that points to the KEVENT. The HANDLE value returned to the application is essentially an index into the handle table. The handle isn’t directly useful to a WDM driver, though, for two reasons. First of all, there isn’t a documented kernel-mode interface for setting an event, given just a handle. Second, and most important, the handle is useful only in a thread that belongs to the same process. If driver code runs in an arbitrary thread (as it often does), it will be unable to reference the event by using the handle.

  4. It’s required to convert the handle to a pointer to the underlying KEVENT object. To handle a METHOD_BUFFERED control operation that the application uses to register an event with the driver

    HANDLE hEvent = *(PHANDLE) Irp->AssociatedIrp.SystemBuffer; PKEVENT pevent; NTSTATUS status = ObReferenceObjectByHandle(hEvent, EVENT_MODIFY_STATE, *ExEventObjectType, Irp->RequestorMode, (PVOID*) &pevent, NULL);

  5. ObReferenceObjectByHandle looks up hEvent in the handle table for the current process and stores the address of the associated kernel object in the pevent variable. If the RequestorMode in the IRP is UserMode, this function also verifies that hEvent really is a handle to something, that the something is an event object, and that the handle was opened in a way that includes the EVENT_MODIFY_STATE privilege.

  6. The Windows service can wait for the event to occur (This takes place in a separate thread created in ServiceMain)

    WaitForSingleObject(hEvent, INFINITE);

  7. The driver signals the event in the usual way
    KeSetEvent(pevent, EVENT_INCREMENT, FALSE);

  8. Eventually, the application cleans up by calling CloseHandle. The driver has a separate reference to the event object, which it must release by calling ObDereferenceObject. The object manager won’t destroy the event object until both these things occur.

That pretty much describes one way to communicate between user and kernel-mode. Two pieces of advice in regards to driver development:

  1. Wrap every entry point in your code
    1. DLL entry points
    2. Driver entry points
    3. Around all accesses to buffers passed to the driver
  2. Never trust a pointer
  3. Take care to protect data structures from thread conflicts
  4. Unload drivers properly and cancel any pending operations
  5. Test your drivers on a virtual machine, please!!!!

if you don’t do driver development the right way then you’ll see quite a few images like the one depicted below


Leave a Reply

Your email address will not be published. Required fields are marked *