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?