KMDF bus driver crash in Pdo_EvtIoInternalDeviceControl

Hello,

I have developped a bus driver (based on the dynamic toaster bus example) that manages a virtual modem and a few virtual COM ports. Whenever one of the child drivers is opened, it sends an IRP_MJ_INTERNAL_DEVICE_CONTROL IRP (asynchronous, allocated with IoAllocateIRP) to its PDO. The corresponding routine in the PDO sets an event to let a user application know that a port/modem has been opened.

Unfortunately, this driver generates a BSOD from time to time in the IRP_MJ_INTERNAL_DEVICE_CONTROL dispatch routine. Here’s my code:

VOID
Bus_Pdo_EvtIoInternalDeviceControl(
IN WDFQUEUE Queue,
IN WDFREQUEST Request,
IN size_t OutputBufferLength,
IN size_t InputBufferLength,
IN ULONG IoControlCode
)
{
NTSTATUS status = STATUS_INVALID_PARAMETER;
WDFDEVICE hDevice;
PPDO_DEVICE_DATA pdoDeviceData;
PFDO_DEVICE_DATA deviceData;
size_t length = 0;
PULONG id;

UNREFERENCED_PARAMETER(OutputBufferLength);

hDevice = WdfIoQueueGetDevice(Queue);
pdoDeviceData = PdoGetData(hDevice);
deviceData = FdoGetData(pdoDeviceData->ParentDevice);

switch (IoControlCode) {
case IOCTL_BUSENUM_MODEM_CONNECTED:
{
status = WdfRequestRetrieveInputBuffer(Request, sizeof(ULONG), &id, &length);
if(!NT_SUCCESS(status) || (length != sizeof(ULONG)) || (*id >= MAX_PORTS)) {
KdPrint((“WdfRequestRetrieveInputBuffer (IOCTL_BUSENUM_MODEM_CONNECTED) failed 0x%x\n”, status));
break;
}

deviceData->OpenPorts.state[*id] = MODEM_ACTIVE;
if(deviceData->ModemEvent != NULL)
KeSetEvent(deviceData->ModemEvent, IO_NO_INCREMENT, FALSE);
KdPrint((“MODEM_ACTIVE %x\n”, *id));
}
break;

//Similar case as above for VComs
(…)

case IOCTL_BUSENUM_PORT_DISCONNECTED:
{
status = WdfRequestRetrieveInputBuffer(Request, sizeof(ULONG), &id, &length);
if(!NT_SUCCESS(status) || (length != sizeof(ULONG)) || (*id >= MAX_PORTS)) {
KdPrint((“WdfRequestRetrieveInputBuffer (IOCTL_BUSENUM_PORT_DISCONNECTED) failed 0x%x\n”, status));
break;
}

deviceData->OpenPorts.state[*id] = 0;
if(deviceData->ModemEvent != NULL)
KeSetEvent(deviceData->ModemEvent, IO_NO_INCREMENT, FALSE);
KdPrint((“PORT_INACTIVE %x\n”, *id));
}
break;

default:
break; // default status is STATUS_INVALID_PARAMETER
}

// Information = 0, nothing to transfer to the output buffer
WdfRequestCompleteWithInformation(Request, status, 0);
}

And one of the stack traces from WinDbg:

IRQL_NOT_LESS_OR_EQUAL (a)
Arguments:
Arg1: 00000016, memory referenced

FAULTING_IP:
nt!KeDelayExecutionThread+261
804e1745 6683781601 cmp word ptr [eax+16h],1

DEFAULT_BUCKET_ID: WRONG_SYMBOLS

BUGCHECK_STR: 0xA

LAST_CONTROL_TRANSFER: from 804e1745 to 804e0aac

STACK_TEXT:
WARNING: Stack unwind information not available. Following frames may be wrong.
f88a6714 804e1745 badb0d00 00000000 8055f214 nt!Kei386EoiHelper+0x2883
f88a67a4 804e20e7 00000000 8266a280 82bd3248 nt!KeDelayExecutionThread+0x261
f88a67b8 f8c1771f 8266a280 00000000 00000000 nt!KeSetEvent+0x5e
f88a67dc f782eba4 81eab10c 7d42cdb0 00000000 baracodabus+0x71f
f88a6800 f782ff7c 7dc27cf8 7d42cdb0 00000000 Wdf01000!FxIoQueueIoDeviceControl::Invoke+0x30
f88a6830 f7832598 7d42cdb0 82bd3248 823d8300 Wdf01000!FxIoQueue::DispatchRequestToDriver+0x445
f88a684c f7833d2c 823d8300 f7859188 823d8300 Wdf01000!FxIoQueue::DispatchEvents+0x485
f88a6868 f7834e67 00000000 81eab028 82df28b0 Wdf01000!FxIoQueue::QueueRequest+0x237
f88a688c f7823d9a 82c22b48 f88a68d4 804e13d9 Wdf01000!FxPkgIo::Dispatch+0x377
f88a6898 804e13d9 82699f00 82c22b48 81eab0e0 Wdf01000!FxDevice::Dispatch+0x7f
f88a68d4 804e13d9 81eab028 826d84e0 00000000 nt!IofCallDriver+0x32
f88a6904 f8af1c51 81eab028 826d84e0 00000001 nt!IofCallDriver+0x32
f88a6a40 f8af1dd4 8225fd20 826d84e0 826d84f0 Modem!UniOpenStarter+0x48b
f88a6a5c 804e13d9 8225fc68 826d84e0 826d84e0 Modem!UniOpen+0x50
f88a6b4c 8056c063 82699f00 00000000 8244b008 nt!IofCallDriver+0x32
f88a6bc4 8056f2a8 00000000 f88a6c04 00000040 nt!SeDeleteAccessState+0x573
f88a6c18 8057d2d3 00000000 00000000 8a6d6401 nt!ObOpenObjectByName+0xda
f88a6c94 8057d3a2 018c951c c0100080 018c94bc nt!FsRtlCurrentBatchOplock+0x2a2
f88a6cf0 8057d3e5 018c951c c0100080 018c94bc nt!IoCreateFile+0x4f
f88a6d30 804dd99f 018c951c c0100080 018c94bc nt!NtCreateFile+0x30
f88a6e0c 8056deaf f88a6d64 0000bbd4 00000000 nt!KiDeliverApc+0xb9e
f88a6e9c 805731ac e18cae94 f88a6ba4 8000000b nt!ProbeForWrite+0x5be
f88a6edc 80550146 fffffffc 00000000 0000001c nt!SeAssignSecurity+0x169
f88a6ee0 fffffffc 00000000 0000001c f88a6ba4 nt!ExAllocatePoolWithTag+0x141

Clearly, EAX == 0, so I am dereferencing a NULL pointer in KeSetEvent. How is it possible, given that I test the ModemEvent variable before?

The PDO queue is sequential:
WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(
&queueConfig,
WdfIoQueueDispatchSequential
);
queueConfig.EvtIoInternalDeviceControl = Bus_Pdo_EvtIoInternalDeviceControl;

so there should not be any risk of race conditions, should be?

Any ideas?

Thanks,
Aleksander Palka
Baracoda