Setting up a non pnp IOCTL queue

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;
    }

You need to assign a name to your control device.

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.

@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.

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 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))

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 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.

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.