cwindowskerneldriverswdm

WDM driver and user-mode communication: best practices and callback questions


The purpose of my driver is to notify a user-mode application about every callback that I receive, passing the data I get from those registered routines to it. The user-mode application will then print on the screen (it's a simple Win32 console application) every information it receives from kernel. I currently register three callbacks: PsSetCreateProcessNotifyRoutineEx, PsSetCreateProcessNotifyRoutineEx2 and PsSetLoadImageNotifyRoutine. I would like to know:

1) Which is the "best" method for communication between kernel-mode and user-mode, considering that many processes and many images can be loaded at the same time?

2) Should I implement such a method for every call or should I store some information and push them to user-mode every, let's say, 0.5 seconds?

I actually use the following code for defining the IOCTL in the driver:

#define IOCTL_RECEIVE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802, METHOD_BUFFERED, FILE_READ_DATA)

This is the code of my DispatchDeviceControl function:

NTSTATUS DispatchDeviceControl(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
    KIRQL CurrentIRQL = KeGetCurrentIrql();
    DbgPrint("DispatchDeviceControl called at IRQL level: %d", CurrentIRQL);

    PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(Irp);
    NTSTATUS status = STATUS_SUCCESS;
    PVOID buffer = Irp->AssociatedIrp.SystemBuffer;
    ULONG inLength = irpsp->Parameters.DeviceIoControl.InputBufferLength;
    ULONG outLength = irpsp->Parameters.DeviceIoControl.OutputBufferLength;
    ULONG returnLength = 0;
    pMsg PMSG = NULL;

    switch (irpsp->Parameters.DeviceIoControl.IoControlCode)
    {   
    case IOCTL_RECEIVE:
        DbgPrint("IOCTL_RECEIVE message sent\n");
        KeWaitForSingleObject(&kEvent, Executive, KernelMode, 0, NULL);
        PMSG = (pMsg)ExInterlockedRemoveHeadList(&listhead, &spinlock);
        // Copy data to the buffer
        RtlCopyMemory((PCHAR)buffer, (PCHAR)PMSG, sizeof(Msg));
        // Release the structure
        ExFreePool(PMSG);
        // Set returnLength
        returnLength = sizeof(Msg);
        break;
    default:
        status = STATUS_INVALID_PARAMETER;
    }
    Irp->IoStatus.Status = status;
    Irp->IoStatus.Information = returnLength;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);

    return status;
}

And the code of one of my callback routines:

_Use_decl_annotations_
VOID prcmPsCreateProcessNotifyRoutineEx2(PEPROCESS Process, HANDLE ProcessId, PPS_CREATE_NOTIFY_INFO CreateInfo)
{
    // If process is exiting, just return immediately
    if (CreateInfo == NULL)
        return;

    pMsg PMSG = (pMsg)ExAllocatePoolWithTag(NonPagedPoolNx, sizeof(Msg), 'prcm');

    if (PMSG == NULL)
    {
        DbgPrint("PsCreateProcessNotifyRoutineEx2: ERROR allocating Pool space for data to be sent to user mode\n");
        return;
    }

    // Fill data
    PMSG->ParentId = NULL;
    PMSG->ProcessId = ProcessId;
    PMSG->FullImageName = NULL;

    ExInterlockedInsertHeadList(&listhead, (PLIST_ENTRY)PMSG, &spinlock);
    KeSetEvent(&kEvent, 0, FALSE);

    return;
}

And finally the struct definition (I included some example values, but the first element is obviously the LIST_ENTRY:

typedef struct {
    LIST_ENTRY listhead;
    HANDLE ParentId;
    HANDLE ProcessId;
    PUNICODE_STRING FullImageName;
} Msg, *pMsg;

For your reference, I correctly call in my DriverEntry function:

KeInitializeEvent(&kEvent, SynchronizationEvent, FALSE);
KeInitializeSpinLock(&spinlock);
InitializeListHead(&listhead);

In my user-mode console application, I create a thread in the main routine and in its relative thread function I constantly check for DeviceIoControl's return value in order to print in real time the information that I get from the driver.

3) A little bit off-topic but I think it is something related: is it normal for me to miss some notifications in user-mode, with this code? Does anybody know why?


Solution

  • OSR wrote a great article, The Inverted Call Model. The basic design of such a system consists of a service, a driver, and some protocol between the two. It works with monolithic drivers as well as layered drivers and only requires that the driver be able to receive device control operations.

    I would also like to mention that you are using both PsSetCreateProcessNotifyRoutineEx and PsSetCreateProcessNotifyRoutineEx2. You should use one or the other. PsSetCreateProcessNotifyRoutineEx2 is supported starting with Windows 10, version 1703 Windows Server 2016. It provides better functionality, so I would recommend to use it if possible.

    You can determine which to use by using MmGetSystemRoutinAddress for dynamic usage of of PsSetCreateProcessNotifyRoutineEx2, if it is unavailable then use PsSetCreateProcessNotifyRoutineEx.