Windows System Software -- Consulting, Training, Development -- Unique Expertise, Guaranteed Results
The free OSR Learning Library has more than 50 articles on a wide variety of topics about writing and debugging device drivers and Minifilters. From introductory level to advanced. All the articles have been recently reviewed and updated, and are written using the clear and definitive style you've come to expect from OSR over the years.
Check out The OSR Learning Library at: https://www.osr.com/osr-learning-library/
I'm learning how to write Windows kernel drivers, however I got stuck on something not so obvious to me.
My driver uses PsSetLoadImageNotifyRoutine
to register a callback that is called on every image (dll) load. Once a image load is registered I put it into a linked list, signal an event and have a user mode application retrieve the information using DeviceIoControl
.
The problem I'm having is that the driver crashes with BAD_POOL_CALLER
. According to windbg the problem is located in my DispatchIoctl
routine. The stacktrace points to nt!ExFreePool
however I don't see who else would have freed the PIMAGE_CALLBACK_INFO2
structure.
I also tried to remove the ExFreePool call but got just another memory error.
Why does my kernel driver crash with BAD_POOL_CALLER
?
VOID ImageCallback( IN PUNICODE_STRING FullImageName, IN HANDLE ProcessId, IN PIMAGE_INFO ImageInfo ) { UNREFERENCED_PARAMETER(ImageInfo); PDEVICE_EXTENSION extension; // // Assign extension variable // extension = (PDEVICE_EXTENSION)g_pDeviceObject->DeviceExtension; PIMAGE_CALLBACK_INFO2 info = (PIMAGE_CALLBACK_INFO2)ExAllocatePoolWithTag(NonPagedPool, sizeof(IMAGE_CALLBACK_INFO2), 1); if (info == NULL) { DbgPrint("STATUS_INSUFFICIENT_RESOURCES"); return; } DbgPrint("FullImageName: %wZ\n", FullImageName); info->FullImageName = FullImageName; DbgPrint("ProcessId: %d\n", ProcessId); info->ProcessId = ProcessId; PushEntryList(&SingleHead, &(info->LinkField)); KeSetEvent(extension->ProcessEvent, 0, FALSE); KeClearEvent(extension->ProcessEvent); } NTSTATUS DispatchIoctl( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) { NTSTATUS ntStatus = STATUS_UNSUCCESSFUL; PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp); PDEVICE_EXTENSION extension = (PDEVICE_EXTENSION)DeviceObject->DeviceExtension; PIMAGE_CALLBACK_INFO pImageCallbackInfo; UNREFERENCED_PARAMETER(extension); // // These IOCTL handlers are the set and get interfaces between // the driver and the user mode app // switch (irpStack->Parameters.DeviceIoControl.IoControlCode) { case IOCTL_PROCOBSRV_ACTIVATE_MONITORING: { ntStatus = ActivateMonitoringHanlder(Irp); break; } case IOCTL_PROCOBSRV_GET_IMAGEINFO: { if (irpStack->Parameters.DeviceIoControl.OutputBufferLength >= sizeof(IMAGE_CALLBACK_INFO)) { pImageCallbackInfo = (PIMAGE_CALLBACK_INFO)Irp->AssociatedIrp.SystemBuffer; PSINGLE_LIST_ENTRY SingleListEntry; SingleListEntry = PopEntryList(&SingleHead); if (SingleListEntry != NULL) { PIMAGE_CALLBACK_INFO2 info = (PIMAGE_CALLBACK_INFO2)CONTAINING_RECORD(SingleListEntry, IMAGE_CALLBACK_INFO2, LinkField); if (info->FullImageName != NULL) { DbgPrint("FullImageName: %wZ\n", info->FullImageName); RtlCopyMemory(pImageCallbackInfo->FullImageName, info->FullImageName->Buffer, info->FullImageName->Length); RtlFreeUnicodeString(info->FullImageName); } pImageCallbackInfo->ProcessId = info->ProcessId; ExFreePool(info); } ntStatus = STATUS_SUCCESS; } break; } default: break; } Irp->IoStatus.Status = ntStatus; // // Set number of bytes to copy back to user-mode // if (ntStatus == STATUS_SUCCESS) Irp->IoStatus.Information = irpStack->Parameters.DeviceIoControl.OutputBufferLength; else Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); return ntStatus; }
Upcoming OSR Seminars | ||
---|---|---|
OSR has suspended in-person seminars due to the Covid-19 outbreak. But, don't miss your training! Attend via the internet instead! | ||
Kernel Debugging | 9-13 Sept 2024 | Live, Online |
Developing Minifilters | 15-19 July 2024 | Live, Online |
Internals & Software Drivers | 11-15 Mar 2024 | Live, Online |
Writing WDF Drivers | 20-24 May 2024 | Live, Online |
Comments
You should always post the !analyze -v output if you want help with a crash...
But in this case the failure seems pretty obvious. The FullImageName argument is not yours to free. If you want to pass it to your device control handler you need to make your own deep copy that you then subsequently free.
-scott
OSR
Nice catch! That solved that problem. I removed the
RtlFreeUnicodeString
call. Now I get another one.Is deleting the RtlFreeUnicodeString the ONLY change you made? There are several possible problems here. Remember, Scott advised you to make a copy of the string. If you're just copying the pointer to the string, you have no guarantee that the pointer is going to remain valid. If the process table changes, it's quite possible that pointer could now be pointing into random memory.
Second, where are you copying the string to? What's the definition of FullImageName in IMAGE_CALLBACK_INFO2? Is that a fixed-length array? You aren't checking that there is enough room in that array before doing your copy. Or, even worse, is it defines as a wchar_t *? Because that means you are using a user-mode address without any validation, and that's always dangerous.
Tim Roberts, [email protected]
Software Wizard Emeritus
Initially that was the only thing I did, hence the !analyze output above. I realised my mistake though and used
RtlInitUnicodeString(&info->FullImageName, FullImageName->Buffer);
. IsRtlInitUnicodeString
enough to copy a string or should I allocate a buffer withExAllocatePoolWithTag
? I tried the former and freed it withRtlFreeUnicodeString(&info->FullImageName);
just to end up with another corrupt pool crash.You need to actually allocate a buffer so that you can free it. Something like:
You should then free dest.Buffer with ExFreePool.
-scott
OSR
> typedef struct _ImageCallbackInfo2 {
> UNICODE_STRING FullImageName;
> HANDLE ProcessId;
> SINGLE_LIST_ENTRY LinkField;
> } IMAGE_CALLBACK_INFO2, *PIMAGE_CALLBACK_INFO2;
OK, but you're passing an IMAGE_CALLBACK_INFO structure from your
user-mode app, and that also contains a UNICODE_STRING field, which
contains a pointer. Who is setting that up? What kind of a pointer are
you going to get? If the user-mode app is setting up that structure
with a pointer to a user-mode buffer, then you have a problem. You
can't allow your kernel driver to use unprotected user-mode addresses
without validation. And, of course, you can't hand a kernel-mode
pointer back to a user-mode app.
The better solution would be:
typedef struct {
HANDLE ProcessId;
wchar_t FullImageName[1024];
} IMAGE_CALLBACK_INFO;
Now, you don't have any pointers at all. The memory access is all
handled by the I/O manager, so it is known to be valid when you get into
your ioctl handler. You'll want to make sure the image name fits before
doing the copy, and you can return a STATUS_BUFFER_OVERFLOW to tell the
app to hand you a bigger buffer.
Tim Roberts, [email protected]
Software Wizard Emeritus