windows - Keyboard filter driver losse BSOD

Indlæg af Hanne Mølgaard Plasc

Problem



Jeg har udviklet en tastaturfilterdriver, der ændrer tastaturknappen '1' (over Q-knappen) til '2'.


Denne driver fungerer fint.


Men efter at Unload er udført, vil du trykke på tastaturknappen BSOD.


Hvis chaufføren er lastet og losset uden at trykke på tastaturknappen, vil den blive aflæst normalt.


Når jeg tjekker det med Windbg, kaldes min driverens ReadCompletion () -funktion, selv efter at den er aflæst.


Jeg ved ikke, hvorfor det sker, selvom jeg har kaldt IoDetachDevice () og IoDeleteDevice ().


Desuden ændres det, når du har lagt i føreren, hvis du trykker på tastaturet '1' i begyndelsen, ændres det ikke til '2'.


Og så ændrer det sig meget godt.


Jeg ved ikke, hvad dette er relateret til.


Jeg håber du vil finde en løsning på dette problem.


Svar venligst på mit spørgsmål.


Nedenfor er kildekoden.


#include <wdm.h>

typedef struct
{
    PDEVICE\_OBJECT NextLayerDeviceObject;
} DEVICE\_EXTENSION, *PDEVICE\_EXTENSION;

const WCHAR next\_device\_name[] = L"\Device\KeyboardClass0";

const char dbg\_name[] = "[Test]";

NTSTATUS IrpSkip(IN PDEVICE\_OBJECT DeviceObject, IN PIRP Irp)
{
    NTSTATUS ret = STATUS\_SUCCESS;
    PIO\_STACK\_LOCATION Stack = IoGetCurrentIrpStackLocation(Irp);

    DbgPrint("\%s IrpSkip() Start
", dbg\_name);
    DbgPrint("\%s IrpSkip() - MajorFunction \%d
", dbg\_name, Stack->MajorFunction);

    IoSkipCurrentIrpStackLocation(Irp);
    ret = IoCallDriver(((PDEVICE\_EXTENSION)(DeviceObject->DeviceExtension))->NextLayerDeviceObject, Irp);
    DbgPrint("IoCallDriver return \%x
", ret);
    DbgPrint("\%s IrpSkip() End
", dbg\_name);

    return ret;
}

NTSTATUS ReadCompletion(IN PDEVICE\_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context)
{
    NTSTATUS ret = STATUS\_SUCCESS;
    PIO\_STACK\_LOCATION Stack;
    unsigned char key[32];

    DbgPrint("\%s ReadCompletion() Start
", dbg\_name);

    if (Irp->IoStatus.Status == STATUS\_SUCCESS)
    {
        DbgPrint("\%s ReadCompletion() - Success
", dbg\_name);
        RtlCopyMemory(key, Irp->AssociatedIrp.SystemBuffer, 32);
        DbgPrint("\%s Data : \%d \%d \%d \%d \%d \%d \%d \%d
", dbg\_name, key[0], key[1], key[2], key[3], key[4], key[5], key[6], key[7]);
        if (key[2] == 2)
        {
            key[2] = 3;
            RtlCopyMemory(Irp->AssociatedIrp.SystemBuffer, key, 32);
            DbgPrint("\%s Key '1' changed '2'
", dbg\_name);
        }
    }
    //else if (Irp->IoStatus.Status == STATUS\_PENDING)
    else
    {
        DbgPrint("\%s ReadCompletion() - Fail... \%x
", Irp->IoStatus.Status);
    }

    if (Irp->PendingReturned)
    {
        IoMarkIrpPending(Irp);
    }

    DbgPrint("\%s ReadCompletion() End
", dbg\_name);

    return Irp->IoStatus.Status;
}

NTSTATUS Read(IN PDEVICE\_OBJECT DeviceObject, IN PIRP Irp)
{
    NTSTATUS ret = STATUS\_SUCCESS;
    PIO\_STACK\_LOCATION Stack = IoGetCurrentIrpStackLocation(Irp);

    DbgPrint("\%s Read() Start
", dbg\_name);

    PDEVICE\_EXTENSION device\_extension = (PDEVICE\_EXTENSION)DeviceObject->DeviceExtension;

    //IoCopyCurrentIrpStackLocationToNext(Irp);
    PIO\_STACK\_LOCATION current\_irp = IoGetCurrentIrpStackLocation(Irp);
    PIO\_STACK\_LOCATION next\_irp = IoGetNextIrpStackLocation(Irp);
    *next\_irp = *current\_irp;

    IoSetCompletionRoutine(Irp, ReadCompletion, DeviceObject, TRUE, TRUE, TRUE);

    ret=IoCallDriver(((PDEVICE\_EXTENSION)device\_extension)->NextLayerDeviceObject, Irp);

    DbgPrint("\%s Read() End
", dbg\_name);

    return ret;
}

NTSTATUS Unload(IN PDRIVER\_OBJECT DriverObject)
{
    NTSTATUS ret = STATUS\_SUCCESS;

    IoDetachDevice(((PDEVICE\_EXTENSION)(DriverObject->DeviceObject->DeviceExtension))->NextLayerDeviceObject);
    IoDeleteDevice(DriverObject->DeviceObject);

    DbgPrint("\%s Unload()...
", dbg\_name);

    return ret;
}

NTSTATUS DriverEntry(IN PDRIVER\_OBJECT DriverObject, IN PUNICODE\_STRING RegistryPath)
{
    NTSTATUS ret=STATUS\_SUCCESS;
    UNICODE\_STRING \_next\_device\_name;

    DbgSetDebugFilterState(DPFLTR\_DEFAULT\_ID, DPFLTR\_INFO\_LEVEL, TRUE);

    DbgPrint("\%s DriverEntry() Start
", dbg\_name);

    RtlInitUnicodeString(&\_next\_device\_name, next\_device\_name);

    for (int i = 0; i < IRP\_MJ\_MAXIMUM\_FUNCTION ; i++)
    {
        DriverObject->MajorFunction[i] = IrpSkip;
    }
    DriverObject->DriverUnload = Unload;
    DriverObject->MajorFunction[IRP\_MJ\_READ] = Read;

    PDEVICE\_OBJECT DeviceObject = 0;
    PDEVICE\_EXTENSION DeviceExtension;

    ret = IoCreateDevice(DriverObject, sizeof(DEVICE\_EXTENSION), 0, FILE\_DEVICE\_KEYBOARD, 0, TRUE, &DeviceObject);
    if (ret == STATUS\_SUCCESS)
    {
        DbgPrint("\%s DriverEntry() - IoCreateDevice() Success
", dbg\_name);
    }
    else
    {
        DbgPrint("\%s DriverEntry() - IoCreateDevice() Fail
", dbg\_name);
        return ret;
    }
    DeviceExtension = (PDEVICE\_EXTENSION)DeviceObject->DeviceExtension;
    DeviceObject->Flags &= ~DO\_DEVICE\_INITIALIZING;
    DeviceObject->Flags |= (DO\_BUFFERED\_IO | DO\_POWER\_PAGABLE);

    ret = IoAttachDevice(DeviceObject, &\_next\_device\_name, &DeviceExtension->NextLayerDeviceObject);
    if (ret == STATUS\_SUCCESS)
    {
        DbgPrint("\%s DriverEntry() - IoAttachDevice() Success
", dbg\_name);
    }
    else
    {
        DbgPrint("\%s DriverEntry() - IoAttachDevice() Fail
", dbg\_name);
        IoDeleteDevice(DriverObject->DeviceObject);
        return ret;
    }

    DbgPrint("\%s DriverEntry() End
", dbg\_name);

    return ret;
}


Nedenfor er Windbg Call Stack.


0: kd> k
 # ChildEBP RetAddr  
00 82f33604 82eea083 nt!RtlpBreakWithStatusInstruction
01 82f33654 82eeab81 nt!KiBugCheckDebugBreak+0x1c
02 82f33a1c 82e4c5cb nt!KeBugCheck2+0x68b
03 82f33a1c 975e36e0 nt!KiTrap0E+0x2cf
WARNING: Frame IP not in any known module. Following frames may be wrong.
04 82f33aac 82e83933 <Unloaded\_Test.sys>+0x16e0
05 82f33af0 8efed7a2 nt!IopfCompleteRequest+0x128
06 82f33b14 8eea7b74 kbdclass!KeyboardClassServiceCallback+0x2fa
07 82f33b78 82e831b5 i8042prt!I8042KeyboardIsrDpc+0x18c
08 82f33bd4 82e83018 nt!KiExecuteAllDpcs+0xf9
09 82f33c20 82e82e38 nt!KiRetireDpcList+0xd5
0a 82f33c24 00000000 nt!KiIdleLoop+0x38


CallBack-funktionen ser ikke ud til at blive frigivet korrekt.


Hvordan løser jeg dette problem?

Bedste reference


hvis du sender pointer til eget førerhus (ReadCompletion i dit tilfælde) - føreren må ikke aflæses, før denne peger er brugt (ReadCompletion kaldet og returnerede din sag)


som meddelt Harry Johnston brug for brug IoSetCompletionRoutineEx - men dokumentation for dette er dårligt og ikke forklarer alle detaljer. absolut obligatoriske undersøgelsesvinduer src-filer (f.eks. WRK-v1.2 og binær windows-kode. Hvis du ser efter implementering af IoSetCompletionRoutineEx - kan du se, at denne rutine ikke gør noget for at forhindre dig i chaufføren til aflæsning. det tildeler simpelthen lille hukommelsesblok, gem dine DeviceObject, Context og CompletionRoutine og indstil IopUnloadSafeCompletion som færdiggørelse og peger til tildelt hukommelsesblok som kontekst. [71] [72]


hvad laver IopUnloadSafeCompletion


NTSTATUS
IopUnloadSafeCompletion(
    IN PDEVICE\_OBJECT DeviceObject,
    IN PIRP Irp,
    IN PVOID Context
    )
{
    PIO\_UNLOAD\_SAFE\_COMPLETION\_CONTEXT Usc = Context;
    NTSTATUS Status;

    ObReferenceObject (Usc->DeviceObject);

    Status = Usc->CompletionRoutine (DeviceObject, Irp, Usc->Context);

    ObDereferenceObject (Usc->DeviceObject);
    ExFreePool (Usc);
    return Status;
}


men det antages, at Usc->DeviceObject ER GÆLDENDE ved at ringe IopUnloadSafeCompletion tid. Du kan slette/de-reference DeviceObject inde i CompletionRoutine, gør en opgave, der får din driver til at losse - og det vil ikke være noget sammenbrud, fordi din CompletionRoutine beskyttet ved at tilføje henvisning til din enhed. men hvis IopUnloadSafeCompletion vil blive kaldt, når din enhed allerede er ødelagt, og chaufføren aflastet - vil der være nogen sammenstød.


delvis -opløsning vil blive ringet ObfReferenceObject(DeviceObject) i din afsendelsesrutine og ObfDereferenceObject(DeviceObject) i færdiggørelsesrutinen. dette på praksis løse problemet. så kode skal være næste


NTSTATUS OnComplete(PDEVICE\_OBJECT DeviceObject, PIRP Irp, PVOID /*Context*/)
{
    ObfDereferenceObject(DeviceObject);// !!!

    PIO\_STACK\_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);

    if (Irp->PendingReturned)
    {
        IrpSp->Control |= SL\_PENDING\_RETURNED;
    }

    if (IrpSp->MajorFunction == IRP\_MJ\_READ &&
        Irp->IoStatus.Status == STATUS\_SUCCESS && 
        (Irp->Flags & IRP\_BUFFERED\_IO))
    {
        if (ULONG n = (ULONG)Irp->IoStatus.Information / sizeof(KEYBOARD\_INPUT\_DATA))
        {
            PKEYBOARD\_INPUT\_DATA pkid = (PKEYBOARD\_INPUT\_DATA)Irp->AssociatedIrp.SystemBuffer;

            do 
            {
                DbgPrint("Port\%x> \%x \%x
", pkid->UnitId, pkid->MakeCode, pkid->Flags);
            } while (pkid++, --n);
        }
    }

    return ContinueCompletion;
}

NTSTATUS KbdDispatch(PDEVICE\_OBJECT DeviceObject, PIRP Irp)
{
    IoCopyCurrentIrpStackLocationToNext(Irp);

    if (0 > IoSetCompletionRoutineEx(DeviceObject, Irp, OnComplete, NULL, TRUE, TRUE, TRUE))
    {
        IoSkipCurrentIrpStackLocation(Irp);
    }
    else
    {
        ObfReferenceObject(DeviceObject);// !!!
    }

    return IofCallDriver(
        reinterpret\_cast<DEVICE\_EXTENSION*>(DeviceObject->DeviceExtension)->\_NextDeviceObject, Irp);
}


ring ObfReferenceObject(DeviceObject); i KbdDispatch forhindrer at losse din chauffør til ObfDereferenceObject(DeviceObject); kaldet indenfor OnComplete.


du kan bede om hvad i dette tilfælde IoSetCompletionRoutineEx overhovedet, hvis vi selv kalder ObfReferenceObject/ObfDereferenceObject? fordi hvis DriverUnload allerede er kaldt - er hele din kode kun indeholdt på en enkelt henvisning på DeviceObject - så når du ringer til ObfDereferenceObject(DeviceObject); fra OnComplete - bliver din enhed slettet og føreren aflastet inde ObfDereferenceObject og til sidst returnerede denne rutine til din unloaded kode. så følelsen af ​​IoSetCompletionRoutineEx er at beskytte din færdiggørelsesrutine. [73] [74]


men har brug for at forstå, at dette alligevel ikke er 100\% korrekt løsning. ring IoDetachDevice/IoDeleteDevice for vedhæftet enhed ikke korrekt fra DriverUnload. ( Dette skal kaldes fra IRP\_MN\_REMOVE\_DEVICE eller FAST\_IO\_DETACH\_DEVICE tilbagekald )


antage det næste scenario - nogen kalder NtReadFile for enhed A, som din B enhed er vedhæftet. NtReadFile få pointer til din B enhed via IoGetRelatedDeviceObject. internt dette rutineopkald IoGetAttachedDevice. læs dette: [75]



   IoGetAttachedDevice øger ikke referenceantalet på
  enhed objekt. (Således er der ikke et matchende opkald til ObDereferenceObject
  kræves.) Opkaldere af IoGetAttachedDevice skal sikre, at ingen enhed
  objekter tilføjes til eller fjernes fra stakken under
   IoGetAttachedDevice udføres. Opkaldere, der ikke kan gøre dette, skal bruge
   IoGetAttachedDeviceReference i stedet.



antage, at mens NtReadFile bruger peger til din B enhed, en anden tråd kaldet din DriverUnload, som sletter B enheden og loser driveren. håndtag/filobjekt eksisterer på enheden A - dette hold det og forhindrer fra aflæsning. men din vedhæftede [[<53]] enhed ikke behøver intet . som resultat hvis NtReadFile eller en anden I/O-delsystemrutine, som bruger din enhed til at udføre samtidig med DriverUnload, hvor du kalder afbryder/sletter enhed - systemet kan gå ned indenfor NtReadFile koden. og du kan intet gøre med dette. kun en vej efter opkald IoDetachDevice nogle (hvor mange?!) tid vente før opkald IoDeleteDevice. heldigvis mulighed for denne sag meget lav sædvanlig.


så prøv at forstå - systemet kan gå ned i NtReadFile allerede. selvom din afsendelse kaldes - din DeviceObject kan slettes/ikke gyldig allerede eller chaufføren aflastet under afsendelsesrutinen. først efter at du har ringet ObfReferenceObject(DeviceObject) bliver alt ok. og alt dette problem, fordi du forsøger at løsne den vedhæftede enhed i DriverUnload (Windows ikke designet til dette).


kan også bemærke mange andre fejl i din kode. sige, at færdiggørelsesrutinen ikke må returnere Irp->IoStatus.Status den skal returnere eller StopCompletion (dvs. STATUS\_MORE\_PROCESSING\_REQUIRED) eller en anden værdi - sædvanlig ContinueCompletion (dvs. STATUS\_CONTINUE\_COMPLETION eller 0) også behøver ikke hardcode "\Device\KeyboardClass0" men brug IoRegisterPlugPlayNotification med GUID\_CLASS\_KEYBOARD hvis du ikke wdm driver. også for xp har brug for speciel håndterer til IRP\_MJ\_POWER (Passing Power IRPs), men det kan måske være, at dette allerede ikke er faktisk, hvis xp-support ikke er faktisk. [76]


kodeeksempel kan se ud:


struct DEVICE\_EXTENSION
{
    PDEVICE\_OBJECT \_NextDeviceObject;
};

NTSTATUS KbdPower(PDEVICE\_OBJECT DeviceObject, PIRP Irp)
{
    PoStartNextPowerIrp(Irp);

    IoSkipCurrentIrpStackLocation(Irp);

    return PoCallDriver(
        reinterpret\_cast<DEVICE\_EXTENSION*>(DeviceObject->DeviceExtension)->\_NextDeviceObject, Irp);
}

NTSTATUS OnComplete(PDEVICE\_OBJECT DeviceObject, PIRP Irp, PVOID /*Context*/)
{
    ObfDereferenceObject(DeviceObject);

    PIO\_STACK\_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);

    if (Irp->PendingReturned)
    {
        IrpSp->Control |= SL\_PENDING\_RETURNED;
    }

    if (IrpSp->MajorFunction == IRP\_MJ\_READ &&
        Irp->IoStatus.Status == STATUS\_SUCCESS && 
        (Irp->Flags & IRP\_BUFFERED\_IO))
    {
        if (ULONG n = (ULONG)Irp->IoStatus.Information / sizeof(KEYBOARD\_INPUT\_DATA))
        {
            PKEYBOARD\_INPUT\_DATA pkid = (PKEYBOARD\_INPUT\_DATA)Irp->AssociatedIrp.SystemBuffer;

            do 
            {
                DbgPrint("Port\%x> \%x \%x
", pkid->UnitId, pkid->MakeCode, pkid->Flags);
            } while (pkid++, --n);
        }
    }

    return ContinueCompletion;
}

NTSTATUS KbdDispatch(PDEVICE\_OBJECT DeviceObject, PIRP Irp)
{
    IoCopyCurrentIrpStackLocationToNext(Irp);

    if (0 > IoSetCompletionRoutineEx(DeviceObject, Irp, OnComplete, NULL, TRUE, TRUE, TRUE))
    {
        IoSkipCurrentIrpStackLocation(Irp);
    }
    else
    {
        ObfReferenceObject(DeviceObject);
    }

    return IofCallDriver(
        reinterpret\_cast<DEVICE\_EXTENSION*>(DeviceObject->DeviceExtension)->\_NextDeviceObject, Irp);
}

NTSTATUS KbdNotifyCallback(PDEVICE\_INTERFACE\_CHANGE\_NOTIFICATION Notification, PDRIVER\_OBJECT DriverObject)
{
    if (::RtlCompareMemory(&Notification->Event, &GUID\_DEVICE\_INTERFACE\_ARRIVAL, sizeof(GUID)) == sizeof(GUID))
    {
        DbgPrint("++\%wZ
", Notification->SymbolicLinkName);

        HANDLE hFile;
        OBJECT\_ATTRIBUTES oa = { sizeof(oa), 0, Notification->SymbolicLinkName, OBJ\_CASE\_INSENSITIVE };
        IO\_STATUS\_BLOCK iosb;

        if (0 <= IoCreateFile(&hFile, SYNCHRONIZE, &oa, &iosb, 0, 0, FILE\_SHARE\_VALID\_FLAGS, FILE\_OPEN, 0, 0, 0, CreateFileTypeNone, 0, IO\_ATTACH\_DEVICE))
        {
            PFILE\_OBJECT FileObject;

            NTSTATUS status = ObReferenceObjectByHandle(hFile, 0, 0, 0, (void**)&FileObject, 0);

            NtClose(hFile);

            if (0 <= status)
            {
                PDEVICE\_OBJECT DeviceObject, TargetDevice = IoGetAttachedDeviceReference(FileObject->DeviceObject);

                ObfDereferenceObject(FileObject);

                if (0 <= IoCreateDevice(DriverObject, sizeof(DEVICE\_EXTENSION), 0, 
                    TargetDevice->DeviceType, 
                    TargetDevice->Characteristics & (FILE\_REMOVABLE\_MEDIA|FILE\_DEVICE\_SECURE\_OPEN), 
                    FALSE, &DeviceObject))
                {
                    DeviceObject->Flags |= TargetDevice->Flags & 
                        (DO\_BUFFERED\_IO|DO\_DIRECT\_IO|DO\_SUPPORTS\_TRANSACTIONS|DO\_POWER\_PAGABLE|DO\_POWER\_INRUSH);

                    DEVICE\_EXTENSION* pExt = (DEVICE\_EXTENSION*)DeviceObject->DeviceExtension;

                    if (0 > IoAttachDeviceToDeviceStackSafe(DeviceObject, TargetDevice, &pExt->\_NextDeviceObject))
                    {
                        IoDeleteDevice(DeviceObject);
                    }
                    else
                    {
                        DeviceObject->Flags &= ~DO\_DEVICE\_INITIALIZING;

                        DbgPrint("++DeviceObject<\%p> \%x
", DeviceObject, DeviceObject->Flags);
                    }
                }

                ObfDereferenceObject(TargetDevice);
            }
        }
    }

    return STATUS\_SUCCESS;
}

PVOID NotificationEntry;

void KbdUnload(PDRIVER\_OBJECT DriverObject)
{
    DbgPrint("KbdUnload(\%p)
", DriverObject);

    if (NotificationEntry) IoUnregisterPlugPlayNotification(NotificationEntry);

    PDEVICE\_OBJECT NextDevice = DriverObject->DeviceObject, DeviceObject;

    while (DeviceObject = NextDevice)
    {
        NextDevice = DeviceObject->NextDevice;
        DbgPrint("--DeviceObject<\%p>
", DeviceObject);
        IoDetachDevice(reinterpret\_cast<DEVICE\_EXTENSION*>(DeviceObject->DeviceExtension)->\_NextDeviceObject);
        IoDeleteDevice(DeviceObject);
    }
}

NTSTATUS KbdInit(PDRIVER\_OBJECT DriverObject, PUNICODE\_STRING /*RegistryPath*/)
{       
    DbgPrint("KbdInit(\%p)
", DriverObject);

    DriverObject->DriverUnload = KbdUnload;

#ifdef \_WIN64
    \_\_stosq
#else
    \_\_stosd
#endif
        ((PULONG\_PTR)DriverObject->MajorFunction, (ULONG\_PTR)KbdDispatch, RTL\_NUMBER\_OF(DriverObject->MajorFunction));

    ULONG MajorVersion;
    PsGetVersion(&MajorVersion, 0, 0, 0);
    if (MajorVersion < 6) DriverObject->MajorFunction[IRP\_MJ\_POWER] = KbdPower; 

    IoRegisterPlugPlayNotification(
        EventCategoryDeviceInterfaceChange,
        PNPNOTIFY\_DEVICE\_INTERFACE\_INCLUDE\_EXISTING\_INTERFACES,
        (void*)&GUID\_CLASS\_KEYBOARD, DriverObject,
        (PDRIVER\_NOTIFICATION\_CALLBACK\_ROUTINE)KbdNotifyCallback,
        DriverObject, &NotificationEntry);

    return STATUS\_SUCCESS;
}

Andre referencer 1


Det lyder som om det forklarer dit problem:



   Bemærk Kun en driver, som kan garantere den, vil ikke blive aflæst, før den afsluttes rutinemæssigt, kan bruge IoSetCompletionRoutine. Ellers skal føreren bruge IoSetCompletionRoutineEx, som forhindrer chaufføren i at aflæse, indtil dens færdiggørelsesrutine udføres.



(Fra MSDN-dokumentationen til IoSetCompletionRoutine.) [77]





PS: En-tastetidsforsinkelsen i den funktionalitet, der træder i kraft, kan forventes, fordi din chauffør ikke er i gang med den læsning, der allerede var i gang, da den blev lastet. Jeg er ikke sikker på, om der er nogen rimelig måde at gøre det.