windowsserial-portdevice-driverbsodwdm

BSOD with Bug Check 0x139 in RemoveEntryList


We have developed a WDM serial port driver which is based on (WinDDK 6) native serial COM port driver.

But our customer has a application triggering BSOD when using our driver.

This application calls IRP_MJ_READ continuously when the button on the program is turned on, and BSOD happened when program is being closed without turning off the button.

We have debugged with WinDBG and found the root cause is RemoveEntryList and the Bug check code tells us we have called RemoveEntryList twice. See Bug check 0x139.

After analyzing, we can't see differences for the codes between our driver and WinDDK, but native COM1 does not trigger BSOD when runing this application.

The related codes are as followings:

When the program is being closed, the system call SerialKillAllReadsOrWrites to kill the pending IRPs in the ReadQueue.

VOID
SerialKillAllReadsOrWrites(
    IN PDEVICE_OBJECT DeviceObject,
    IN PLIST_ENTRY QueueToClean,
    IN PIRP *CurrentOpIrp
    )
{

    KIRQL cancelIrql;
    PDRIVER_CANCEL cancelRoutine;

    IoAcquireCancelSpinLock(&cancelIrql);

    //
    // Clean the list from back to front.
    //
    while (!IsListEmpty(QueueToClean)) {

        PIRP currentLastIrp = CONTAINING_RECORD(
                                  QueueToClean->Blink,
                                  IRP,
                                  Tail.Overlay.ListEntry
                                  );

        RemoveEntryList(QueueToClean->Blink);

        cancelRoutine = currentLastIrp->CancelRoutine;
        currentLastIrp->CancelIrql = cancelIrql;
        currentLastIrp->CancelRoutine = NULL;
        currentLastIrp->Cancel = TRUE;

        cancelRoutine(
            DeviceObject,
            currentLastIrp
            );               // <- call SerialCancelQueued()

        IoAcquireCancelSpinLock(&cancelIrql);

    }

    .
    .
    .
}
VOID
SerialCancelQueued(
    PDEVICE_OBJECT DeviceObject,
    PIRP Irp
    )
{

    PSERIAL_DEVICE_EXTENSION extension = DeviceObject->DeviceExtension;
    PIO_STACK_LOCATION irpSp = IoGetCurrentIrpStackLocation(Irp);

    SERIAL_LOCKED_PAGED_CODE();

    Irp->IoStatus.Status = STATUS_CANCELLED;
    Irp->IoStatus.Information = 0;
    RemoveEntryList(&Irp->Tail.Overlay.ListEntry); // <- BSOD happened here!
    .
    .
    .
}

We found the first call of RemoveEntryList in SerialKillAllReadsOrWrites and the second call in SerialCancelQueued are going to remove the same entry.

And we have tested that if we mark the first RemoveEntryList, it passed, no longer BSOD.

But why native COM doesn't trigger BSOD even for calling RemoveEntryList twice to remove the same entry?

Could someone help me to understand why? Thanks.


Solution

  • I found RemoveEntryList in WDK8.1 is different in WDK6. If I build driver by WDK6, Windows will not trigger BSOD when we call RemoveEntryList twice. However, if driver is built by WDK8.1, Windows will trigger BSOD when we call RemoveEntryList twice. So, maybe the original codes in SerialKillAllReadsOrWrites should be modified to avoid calling RemoveEntryList twice if we want to build driver by WDK8.1.

    // WDK6:
    FORCEINLINE
    BOOLEAN
    RemoveEntryList(
        _In_ PLIST_ENTRY Entry
        )
    
    {
    
        PLIST_ENTRY Blink;
        PLIST_ENTRY Flink;
    
        Flink = Entry->Flink;
        Blink = Entry->Blink;
        Blink->Flink = Flink;
        Flink->Blink = Blink;
        return (BOOLEAN)(Flink == Blink);
    }
    
    // WDK 8.1
    FORCEINLINE
    BOOLEAN
    RemoveEntryList(
        _In_ PLIST_ENTRY Entry
        )
    
    {
    
        PLIST_ENTRY PrevEntry;
        PLIST_ENTRY NextEntry;
    
        NextEntry = Entry->Flink;
        PrevEntry = Entry->Blink;
        if ((NextEntry->Blink != Entry) || (PrevEntry->Flink != Entry)) {
            FatalListEntryError((PVOID)PrevEntry,
                                (PVOID)Entry,
                                (PVOID)NextEntry);
        }
    
        PrevEntry->Flink = NextEntry;
        NextEntry->Blink = PrevEntry;
        return (BOOLEAN)(PrevEntry == NextEntry);
    }