Windows System Software -- Consulting, Training, Development -- Unique Expertise, Guaranteed Results

Home NTDEV

Before Posting...

Please check out the Community Guidelines in the Announcements and Administration Category.

More Info on Driver Writing and Debugging


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/


Setting up a non pnp IOCTL queue

IgnoreExceptionIgnoreException Member Posts: 47

I am trying to get an IOCTL queue to work for non pnp drivers. I got this working with my mouse filter driver but cant with a generic driver. The point of this driver is that it needs me to manually start it each time I turn my VM on in order to avoid issues with BSOD. Anyways I am getting the following issue that is causing my DriverEntry function to not complete correctly WdfDeviceCreate failed with status 0xc0000079. This seems to be an issue with my security descriptor but I can't seem to find a solution.

    NTSTATUS status = STATUS_SUCCESS;
    WDFDRIVER driver;
    PDEVICE_OBJECT deviceObject;
    WDFDEVICE device = NULL;
    WDF_DRIVER_CONFIG config;
    WDF_IO_QUEUE_CONFIG ioQueueConfig;
    WDF_OBJECT_ATTRIBUTES fdoAttributes;

    DebugMessage("Start DriverEntry \n");

    WDF_DRIVER_CONFIG_INIT(&config, NULL);

    if (!NT_SUCCESS(status = WdfDriverCreate(DriverObject, RegistryPath, WDF_NO_OBJECT_ATTRIBUTES, &config, &driver))) { //WDF_NO_OBJECT_ATTRIBUTES
        DebugMessage("WdfDriverCreate failed with status 0x%x\n", status);
        return status;
    }

    if (driver == NULL) {
        DebugMessage("Driver is null \n");
        return STATUS_UNSUCCESSFUL;
    }

    PWDFDEVICE_INIT deviceInit = WdfControlDeviceInitAllocate(driver, &SDDL_DEVOBJ_SYS_ALL_ADM_RWX_WORLD_RW_RES_R); //&SDDL_DEVOBJ_KERNEL_ONLY);
    if (deviceInit == NULL) { // Allocate a device initialization structure
        DebugMessage("DeviceInit is null \n");
        return STATUS_UNSUCCESSFUL;
    }

    WdfDeviceInitSetCharacteristics(deviceInit, FILE_DEVICE_SECURE_OPEN, FALSE);  // Set the device characteristics

    // Create a framework device object. This call will in turn create a WDM deviceobject, attach to the lower stack and set the appropriate flags and attributes.
    if (!NT_SUCCESS(status = WdfDeviceCreate(&deviceInit, WDF_NO_OBJECT_ATTRIBUTES, &device))) {
        DebugMessage("WdfDeviceCreate failed with status 0x%x\n", status);
        return status;
    }

    if (status == STATUS_SUCCESS) {
        WdfControlFinishInitializing(device); // Initialization of the framework device object is complete
        deviceObject = WdfDeviceWdmGetDeviceObject(device); // Get the associated WDM device object
    }

    WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&ioQueueConfig, WdfIoQueueDispatchParallel);

    if (!NT_SUCCESS(status = WdfIoQueueCreate(device, &ioQueueConfig, WDF_NO_OBJECT_ATTRIBUTES, WDF_NO_HANDLE))) {
        DebugMessage("WdfIoQueueCreate failed 0x%x\n", status);
    }

    if (!NT_SUCCESS(status = CreateMouseInputControlDevice(driver))) { // This will actually initialize my queue
        DebugMessage("CreateMouseInputControlDevice failed with status 0x%x\n", status);
        return status;
    }

Comments

  • Mark_RoddyMark_Roddy Member - All Emails Posts: 4,753

    You need to assign a name to your control device.

  • Tim_RobertsTim_Roberts Member - All Emails Posts: 14,831

    Just to make sure you're clear, WdfDeviceInitSetCharacteristics has a very unusual interface. Passing "FILE_DEVICE_SECURE_OPEN, FALSE" does not clear that bit, as you might expect. Instead, it REPLACES the current characteristics with the set you provide. Since "FILE_DEVICE_SECURE_OPEN" is set by default, it's not clear that line does anything useful.

    Tim Roberts, [email protected]
    Software Wizard Emeritus

  • IgnoreExceptionIgnoreException Member Posts: 47

    @Mark_Roddy said:
    You need to assign a name to your control device.

    Oh thanks!! I forgot to move it ahead of my WdfDeviceCreate function. I now can receive IOCTL messages between user space and my driver but I ran into an issue where my driver incorrectly receives the sent data. In my user space program I do the following to send my struct in the form of a PVOID buffer to my driver.

            const int size = sizeof(TestData);
            TestData* testData = malloc(size);
    
            if (testData) {
                testData->intilize = true;
                testData->name = "IoPractice";
                testData->size = size;
                testData->value = &value;
    
                memReader_write(testData);
                free(testData);
            }
    
    void memReader_write(const PVOID buffer)
    {
        const int size = sizeof(buffer);
        memReader_io_control(WRITE, (PVOID)buffer, (DWORD)size, NULL, 0, GENERIC_WRITE);
    }
    
    void memReader_io_control(DWORD code, PVOID in, DWORD in_size, PVOID out, DWORD out_size, DWORD security)
    {
        HANDLE ra_handle = INVALID_HANDLE_VALUE; // GENERIC_READ | GENERIC_WRITE
        ra_handle = CreateFileW(L"\\\\.\\MemReader", security, 0, 0, OPEN_EXISTING, 0, 0);
    
        if (ra_handle == INVALID_HANDLE_VALUE) {
            printf("Invalid CreateFileW \n");
            return;
        }
    
        DWORD dummy;
        BOOL success = DeviceIoControl(ra_handle, code, in, in_size, out, out_size, &dummy, NULL);;
        BOOL closeSucess = CloseHandle(ra_handle);
    }
    

    Then from my driver I do the following...

    const int SIZEOF_BASE = sizeof(TestData*);
    
    if (!NT_SUCCESS(status = WdfRequestRetrieveInputBuffer(Request, SIZEOF_BASE, &buffer, &buffer_length))) {
         DebugMessage("RetrieveInputBuffer failed: 0x%x\n", status);
         break;
     }
    
                if (buffer == NULL) {
                    status = STATUS_CANCELLED;
                    DebugMessage("KMDF Buffer is null \n");
                    break;
                }
    
                TestData* input = (TestData*)buffer;
                if (input == NULL) {
                    status = STATUS_CANCELLED;
                    DebugMessage("TestData is null \n");
                    break;
                }
    
                const int size = sizeof(*input);
                if (input->size != size) {
                    status = STATUS_CANCELLED;
                    DebugMessage("KMDF MouseData Size Error  Size:%d InputSize:%d \n", size, input->size);
                    break;
                }
    

    Here SIZEOF_BASE should just equal the size of a pointer not the actual size of TestData since I am receiving a PVOID and its parameter inside of WdfRequestRetrieveOutputBuffer wants the minimum size. I then cast the PVOID to a pointer of TestData since that is what I sent but when I compare the sizes aka input->size != size there is a discrepancy where input->size equals either 0 or some other random large number. I assume this may because of using malloc since that wont always clear old memory but I have used this same structure in the past and it has worked fine so I am not sure what is going on. I am fairly sure I cant send the TestData struct directly and instead need to send a pointer but please let me know thanks.

  • Tim_RobertsTim_Roberts Member - All Emails Posts: 14,831
    edited July 2023

    There are a number of issues here. First, I'd like to know the numeric value of your ioctl code, because that will determine how the buffers are transferred.

    Second, the way you are sending the buffer is absolutely wrong. If you want to send a structure through an ioctl, you need to pass the address of the structure (which you are), and the size of the structure (which you are not). Remember, the kernel driver does not get the addresses that you pass to the ioctl. Instead, the data from the input buffer is COPIED into kernel memory. In your case, you'll only get the first 8 bytes of the structure, rendering it useless. You need to pass "sizeof(TestData)" to DeviceIoControl.

    Third, you do not want to include any pointers in your ioctl buffers. I'm specifically looking at "testData->name". Your structure will contain the user-mode address of that string, but a user-mode address is only valid in the context of that process, and by default KMDF dispatch routines do not execute in the original context. Your kernel driver can't get it (except by using extraordinary measures). If you truly need to pass a string, then put a char[] into the struct, and use strcpy to copy it in.

    Tim Roberts, [email protected]
    Software Wizard Emeritus

  • IgnoreExceptionIgnoreException Member Posts: 47

    @Tim_Roberts said:
    There are a number of issues here. First, I'd like to know the numeric value of your ioctl code, because that will determine how the buffers are transferred.

    Second, the way you are sending the buffer is absolutely wrong. If you want to send a structure through an ioctl, you need to pass the address of the structure (which you are), and the size of the structure (which you are not). Remember, the kernel driver does not get the addresses that you pass to the ioctl. Instead, the data from the input buffer is COPIED into kernel memory. In your case, you'll only get the first 8 bytes of the structure, rendering it useless. You need to pass "sizeof(TestData)" to DeviceIoControl.

    Third, you do not want to include any pointers in your ioctl buffers. I'm specifically looking at "testData->name". Your structure will contain the user-mode address of that string, but a user-mode address is only valid in the context of that process, and by default KMDF dispatch routines do not execute in the original context. Your kernel driver can't get it (except by using extraordinary measures). If you truly need to pass a string, then put a char[] into the struct, and use strcpy to copy it in.

    Oh very useful information I appreciate you taking your time to explain that to me. I do want to read the string form of name so I will change it from PCHAR to char[] but I do in fact want the virtual address of 'value' (idc about the actual value of the variable denotes as value) so I will keep that an int pointer. That is unless you have a better solution for sending the virtual memory's address of a given variable.

    I also made it so that the in_size now is set as the size of the struct TestData. Lastly, in terms of my IOCTL code I use this which is equal to WRITE inside my user program (ULONG)(CTL_CODE(0x8888u, 0x889, METHOD_BUFFERED, FILE_ANY_ACCESS))

  • Tim_RobertsTim_Roberts Member - All Emails Posts: 14,831

    I don't think you understand. You will not have the "virtual address" of anything in that structure. The I/O system makes a COPY of the input buffer in kernel memory. You will not have access to anything in the calling process. Even if you pass an "int *", you cannot dereference that pointer in your ioctl handler. The process in memory will not be the one that called you, so any address you get doesn't mean anything. You need to think about your problem differently.

    Tim Roberts, [email protected]
    Software Wizard Emeritus

  • IgnoreExceptionIgnoreException Member Posts: 47
    edited July 2023

    @Tim_Roberts said:
    I don't think you understand. You will not have the "virtual address" of anything in that structure. The I/O system makes a COPY of the input buffer in kernel memory. You will not have access to anything in the calling process. Even if you pass an "int *", you cannot dereference that pointer in your ioctl handler. The process in memory will not be the one that called you, so any address you get doesn't mean anything. You need to think about your problem differently.

    No I am working on practicing translating virtual memory into physical memory to better understand how everything works so I need the the virtual address and my test processes name. I then get the Eprocess in order to get its cr3 value before then going through page tables in order to arrive at the physical address (where the the int* is actually held in physical memory). But I need a better way of sending virtual addresses maybe assign the virtual address to a long and then read the long as if its a pointer (by that I mean treat the value as the address).

    Also I tried using PCHAR (which is what 'name' is) and it worked fine my driver was able to read the string just fine so I dont think I need to use a char array.

  • Tim_RobertsTim_Roberts Member - All Emails Posts: 14,831

    It depends on how your KMDF dispatching is configured. By default, KMDF returns to the caller immediately and schedules your dispatch routine in a DPC, which runs as the system process. If you use a EvtWdfIoInCallerContext callback, that will be called while the calling process is still current, so the addresses are valid.

    You ALWAYS need to be cognizant of whether you are working with a user-mode virtual address or a kernel-mode virtual address. User-mode virtual addresses can go bad at any time, if the process should be killed, and referencing such an address causes a kernel page fault.

    If the process IS current, you don't have to go through EPROCESS to get CR3. There's an intrinsic function that reads the CR3 register directly:

    unsigned __int64 cr3 = __readcr3();
    

    HOWEVER, since CR3 itself contains a physical address, it's not at all clear what good that will do you.

    Tim Roberts, [email protected]
    Software Wizard Emeritus

Sign In or Register to comment.

Howdy, Stranger!

It looks like you're new here. Sign in or register to get started.

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 13-17 May 2024 Live, Online
Developing Minifilters 1-5 Apr 2024 Live, Online
Internals & Software Drivers 11-15 Mar 2024 Live, Online
Writing WDF Drivers 20-24 May 2024 Live, Online