Using IRP_MJ_DEVICE_CONTROL in minifitler

Hi,

I am trying to understand how to use IRP_MJ_DEVICE_CONTROL in a minifilter. I added this to the FLT_OPERATION_REGISTRATION and also added a new custom ioctl which is using buffered io.

When I try to call DeviceIoControl with my custom ioctl on a file that is on a volume which the minifilter is attached to, the output buffer is not populated. In the preoperation handler I get the control and can access the user buffer. I am setting the output and the IoStatus.Information for the bytes returned as well as completing the ioctl with FLT_PREOP_COMPLETE. The caller of DeviceIoControl gets the correct bytes returned but the output buffer is always null. From my understanding when calling using buffered io the buffer (Data->Iopb->Parameters.FileSystemControl.Buffered.SystemBuffer) should be copied back to the users buffer.

On the other hand if just use the DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] and create a device this works as I would expect.

Using the file on the filesystem has the semantics I am looking for as the target object is already set. This seems like the way to go for my situation but I am not understanding why the out buffer is not being set. I could add this as input to the DriverDevice DeviceControl but I do not understand why this is different.

Can someone explain to me why this is not being set or if I should just use the DriverObject->MajorFunction implementation instead?

Thanks for any help with this.

Just to clarify: Cbd->Iopb->Parameters.DeviceIoControl.Buffered.OutputBufferLength is non-zero and Cbd->Iopb->Parameters.DeviceIoControl.Buffered.SystemBuffer is NULL?

@Mark_Roddy

Yes that is correct. The OutputBufferLength is non 0 (8 bytes) and verified that the SystemBuffer is valid.

#define IOCTL_MY_FILTER_TEST_BUFFERED
CTL_CODE(MY_FILTER_TYPE, 0x801, METHOD_BUFFERED, FILE_READ_DATA)

if(Cbd->Iopb->Parameters.DeviceIoControl.Common.IoControlCode == IOCTL_MY_FILTER_TEST_BUFFERED) {
  if(Cbd->Iopb->Parameters.DeviceIoControl.Buffered.OutputBufferLength > sizeof(MyStruct) {
    MyStruct* p = (MyStruct*)Cbd->Iopb->Parameters.DeviceIoControl.Buffered.SystemBuffer;
    p->field1 = 0xff;
    p->field2 = 0x07;

    Cbd->IoStatus.Status = STATUS_SUCCESS;
    Cbd->IoStatus.Information = sizeof(MyStruct);

    // not sure if this is needed but marking callback dirty just in case
    FltSetCallbackDataDirty(data);
  }
  return FLT_PREOP_COMPLETE;
}

return FLT_PREOP_SUCCESS_NO_CALLBACK;

The interesting thing to me is that when the DeviceIoControl returns, the bytesReturned is correctly set to the value of Cbd->IoStatus.Information but the callers buffer is not modified.

Just to point out the obvious, the caller needs to specify both an input buffer and an output buffer. With METHOD_BUFFERED, the system COPIES from the user's input buffer to the SystemBuffer, then when it's done, it COPIES from the SystemBuffer to the user's output buffer. You can't just pass one buffer, because you don't actually get a direct mapping to the user's input buffer.

Yes The caller of DeviceIoControl is specifying buffers for both input and output.

This works fine if the caller opens the driver device and sends the ioctl. This will go to the DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] callback and all works as expected.

If the caller opens the file and sends the ioctl to the file, the minifilter will get the preoperation callback for the IRP_MJ_DEVICE_CONTROL. The same logic in the minifilter peop callback the output buffer will not get set yet the output size does. I am trying to understand why the difference.

I don't know what the problem is, but here is a suggestion: your ioctl may be reaching your filter as a fastio operation and the output buffer is not where you think it is.
See FLT_IS_FASTIO_OPERATION and consider using FltDecodeParameters to get the output buffer address.

Alternatively you can reject fastio operations and require all or some operations to be IRP based.

1 Like

Thanks @Mark_Roddy that was it. It was being issued in the fast io path. Once I used the Data->Iopb->Parameters.DeviceIoControl.FastIo.OutputBuffer it worked as I would expect.

What is the best way to reject the fastio path? Can I just return FLT_PREOP_SUCCESS_NO_CALLBACK so that it get sent to the next filter or is there a better way to explicitly reject the fastio all together?

FLT_PREOP_DISALLOW_FASTIO - but see the docs here for how to correctly return this: https://learn.microsoft.com/en-us/windows-hardware/drivers/ifs/disallowing-a-fast-i-o-operation-in-a-preoperation-callback-routine

Perfect. Thanks for the pointer.