Correct way to forward METHOD_NEITHER in KMDF

Context: I’m a Windows kernel driver newbie trying to use KMDF to build a filter driver, specifically to intercept a specific IOCTL_KS_PROPERTY request on an audio driver stack. My understanding is that KS involves lots of METHOD_NEITHER IOCTLs (including the one I’m interested in), hence this question.

According to MSDN, handling “neither” I/O in KMDF is somewhat subtle. The documentation describes in detail how to do it assuming that my driver wants to handle the request itself. But what if I simply want to forward it down to the next driver down the stack? The documentation doesn’t mention that case.

I looked at various samples, including the toaster filter sample and OSR’s very own GenFilter sample. In both cases, IoDeviceControl requests are simply forwarded in the usual KMDF way with no special consideration given to potential METHOD_NEITHER requests.

I don’t understand how that approach could possibly be correct. EvtIoDeviceControl runs in arbitrary thread context. By the time that request handler is called, the original thread context might already be gone, and attempts to forward the request as-is to the next driver down the stack might just result in that driver accessing an invalid buffer.

This brings me to the conclusion that any filter driver that implements EvtIoDeviceControl and could potentially be confronted with METHOD_NEITHER IOCTLs (including ones it might not know exist!) has to implement an EvtIoInCallerContext callback and go through the whole process of locking the user buffer. (As a side note I’m not sure I understand why KMDF can’t do that automatically…) One example of that process is the NONPNP sample, but note that is not a filter driver.

So let’s say I implement EvtIoInCallerContext and go through this whole dance. Then I want to forward the request to the next-lower driver. But wait, I can’t simply use WDF_REQUEST_SEND_OPTION_SEND_AND_FORGET or WdfRequestFormatRequestUsingCurrentType and call it a day, can I? I would still be forwarding the original user buffer which might not be usable. So… what now? Do I swap the buffer before forwarding the request, so that the I/O target gets my buffer obtained from WdfMemoryGetBuffer()?

Given this rabbit hole I find myself into and my complete lack of experience, I am starting to doubt myself. Am I overthinking all this and trying to solve a problem that doesn’t exist? Am I missing something here?

I will get spanked for saying this, but EvtIoDeviceControl only runs in a system thread context if you have enabled SyncronizationScope on your queue or if you set ExecutionLevel. If you set SynchronizationScope to WdfSynchronizationScopeNone, then your handler will run in the caller context and you can fire-and-forget. It would be unfriendly for filter drivers to alter the context so much.

Filtering the KS ioctls is complicated. The rules are not documented, and because of that they actually DO change from time to time. Making it even more complicated, there is a filter driver above you called “ksthunk.sys” that actually does map some of the user addresses into kernel mode, so your IRPs might already be converted.

For your own processing, the input buffer is always in UserBuffer. You can use WdfRequestRetrieveUnsafeUserInputBuffer to fetch that. If that is a kernel address (meaning the pointer is < 0), then someone has done the mapping work for you. Otherwise, you need to call WdfRequestProbeAndLockUserBufferForRead. I save the resulting pointer in a request context to fetch later.

If the output buffer has already been converted, it will be in the SystemBuffer field (WdfRequestWdfGetIrp(Request)->AssociatedIrp.SystemBuffer). If that is non-zero, you can use it. Otherwise, you need to call WdfRequestRetrieveUnsafeUserOutputBuffer and call WdfRequestProbeAndLockUserBufferForWrite on it.

The input buffer contains the “property descriptor”, usually a KSPROPERTY structure or KSP_PIN structure. The output buffer contains the “property value type”.

1 Like

Both WDF_REQUEST_SEND_OPTION_SEND_AND_FORGET or WdfRequestFormatRequestUsingCurrentType from EvtIoInCallerContext will send the request down the stack in the same context (the user mode process). You mention buffer swapping, is that a feature you want the filter to implement or as a way to compensate for context problems you are anticipating?

If you can describe the higher level functionality and problem you are trying to solve in the filter, you will get better, more concrete guidance. For instance, EvtWdfDeviceWdmIrpPreprocess is also called in the sender’s context and you can accomplish the same inspect + fire & forget from this callback without the WDFREQUEST abstraction. But without understanding what you are trying to do, it is hard to recommend one over the other.

1 Like

@Tim_Roberts said:
I will get spanked for saying this, but EvtIoDeviceControl only runs in a system thread context if you have enabled SyncronizationScope on your queue or if you set ExecutionLevel. If you set SynchronizationScope to WdfSynchronizationScopeNone, then your handler will run in the caller context and you can fire-and-forget. It would be unfriendly for filter drivers to alter the context so much.

Stopping the queue (manually or for a power managed queue, during a device power state transition) will also change the context of EvtIoDeviceControl, even for a ScopeNone WDFQUEUE.

1 Like

(Sorry if this ends up being a double post. I previously posted a response, and then tried to edit it, but that made my post disappear with a warning that it would be “queued for moderation” (?!). I can’t figure out what happened to my original post so I’m reposting just in case.)

@Tim_Roberts said:
I will get spanked for saying this, but EvtIoDeviceControl only runs in a system thread context if you have enabled SyncronizationScope on your queue or if you set ExecutionLevel. If you set SynchronizationScope to WdfSynchronizationScopeNone, then your handler will run in the caller context and you can fire-and-forget. It would be unfriendly for filter drivers to alter the context so much.

Thanks, I expected as much. Seems like this would be relying on undocumented behaviour, but I guess one could easily invoke Hyrum’s law given the number of KMDF filter drivers that might already be relying (perhaps unknowingly) on being able to transparently forward any IOCTL, including METHOD_NEITHER, from within EvtIoDeviceControl.

Filtering the KS ioctls is complicated. The rules are not documented, and because of that they actually DO change from time to time. Making it even more complicated, there is a filter driver above you called “ksthunk.sys” that actually does map some of the user addresses into kernel mode, so your IRPs might already be converted.

For your own processing, the input buffer is always in UserBuffer. You can use WdfRequestRetrieveUnsafeUserInputBuffer to fetch that. If that is a kernel address (meaning the pointer is < 0), then someone has done the mapping work for you. Otherwise, you need to call WdfRequestProbeAndLockUserBufferForRead. I save the resulting pointer in a request context to fetch later.

If the output buffer has already been converted, it will be in the SystemBuffer field (WdfRequestWdfGetIrp(Request)->AssociatedIrp.SystemBuffer). If that is non-zero, you can use it. Otherwise, you need to call WdfRequestRetrieveUnsafeUserOutputBuffer and call WdfRequestProbeAndLockUserBufferForWrite on it.

The input buffer contains the “property descriptor”, usually a KSPROPERTY structure or KSP_PIN structure. The output buffer contains the “property value type”.

Thanks, I was going to tackle that part next. I appreciate the warning about getting both kinds of buffers - I definitely would not have expected that!

@Doron_Holan said:
Both WDF_REQUEST_SEND_OPTION_SEND_AND_FORGET or WdfRequestFormatRequestUsingCurrentType from EvtIoInCallerContext will send the request down the stack in the same context (the user mode process).

Oh, nice! I didn’t know that was a possibility. That would nicely take care of the problem. I can’t find any documentation that says I can forward a request to the local I/O target from within EvtIoInCallerContext, but I can’t find any documentation that says I can’t, either… I guess I was worried that I might confuse the framework by neither completing the request nor enqueuing it before returning from the callback.

By the way, it looks like most KMDF filter drivers would benefit from using this approach, because most filter drivers (presumably) want to transparently and generically forward any IOCTL that it does not understand. Since the filter driver doesn’t know what kind of IOCTL it might be called upon to forward, it has to assume the worst case, i.e. METHOD_NEITHER, and therefore forward any unknown IOCTLs in EvtIoInCallerContext, not EvtIoDeviceControl. If that conclusion is correct, then there’s a number of sample KMDF filter drivers out there that need updating, and possibly a number of real drivers too…

You mention buffer swapping, is that a feature you want the filter to implement or as a way to compensate for context problems you are anticipating?

The latter. I thought I would be forced to do it, but from this discussion it looks like that won’t actually be necessary.

If you can describe the higher level functionality and problem you are trying to solve in the filter, you will get better, more concrete guidance.

The business logic I want to implement in my filter driver is extremely trivial. I only want to listen for a very specific kind of IOCTL (IOCTL_KS_PROPERTY), and if I get one, register a completion routine that makes a small change to the contents of the output buffer before it’s returned to the application. That’s it. All other requests should be forwarded transparently, fire-and-forget style.

For instance, EvtWdfDeviceWdmIrpPreprocess is also called in the sender’s context and you can accomplish the same inspect + fire & forget from this callback without the WDFREQUEST abstraction. But without understanding what you are trying to do, it is hard to recommend one over the other.

Thanks. I doubt that will be necessary - looks like I can achieve my goals without dealing with WDM directly. I’ll keep that option in mind though in case I’m mistaken.

@Doron_Holan said:
Stopping the queue (manually or for a power managed queue, during a device power state transition) will also change the context of EvtIoDeviceControl, even for a ScopeNone WDFQUEUE.

My understanding is that filter drivers only use non-power-managed queues. So, assuming that I never stop the queue, EvtIoDeviceControl should run in the original thread context, right? Though obviously your proposed approach of using EvtIoInCallerContext still seems cleaner since it would make that a strong guarantee instead of relying on undocumented behaviour.

You’ve gotten some excellent advice already. Let’s see if I can fill things out a bit more.

… requests are simply forwarded in the usual KMDF way with no special consideration given to potential METHOD_NEITHER requests.

I don’t understand how that approach could possibly be correct.

Ah! Well, what you may not realize is that except for the file system stack, METHOD_NEITHER IOCTLs are pretty rare. This is because of the need to handle them in the context of the requesting process, the mechanics that are required to properly validate and access the buffers, and the need to “capture” the buffer contents if the buffer contains anything other than pure data (to prevent TOCU issues). When you couple this with the speed at which modern processor implement block move operations, most things that you would use METHOD_NEITHER for are better implemented at METHOD_BUFFERED or METHOD_DIRECT.

Seems like this would be relying on undocumented behaviour, but I guess one could easily invoke Hyrum’s law given the number of KMDF filter drivers that might already be relying (perhaps unknowingly) on being able to transparently forward any IOCTL, including METHOD_NEITHER, from within EvtIoDeviceControl.


Assuming that your EvtIoDeviceControl Event Processing Callback will run in the context of the requesting process relies on an artifact of implementation. Given that there has always been an architecturally specified way to have your driver called in the context of the requesting process, I would say that relying on the implementation detail (assuming EvtIoDeviceControl is sometimes called in the current process’ context) is a serious bug and I would strongly advise you not to do it.


I would hope that SDV would catch this and flag it.


By the way, it looks like most KMDF filter drivers would benefit from using this approach, because most filter drivers (presumably) want to transparently and generically forward any IOCTL that it does not understand. Since the filter driver doesn’t know what kind of IOCTL it might be called upon to forward, it has to assume the worst case, i.e. METHOD_NEITHER, and therefore forward any unknown IOCTLs in EvtIoInCallerContext, not EvtIoDeviceControl. If that conclusion is correct, then there’s a number of sample KMDF filter drivers out there that need updating, and possibly a number of real drivers too…


Like I said above, in the real world, this really isn’t an issue. There’s little use of METHOD_NEITHER. And one doesn’t generally write “generic” filter drivers, or filter drivers for branches of the device tree that one does not understand.


When I’ve seen METHOD_NEITHER used (in 3rd party AND in in-box MSFT developed drivers) it is generally sign of a bad design, or an outright error. We once did a project where we closed almost 100 high-priority security issues that were caused by gratuitous use of METHOD_NEITHER. Fortunately, most of these bad uses have been purged and in the 21st century folks don’t use METHOD_NEITHER when something else will do.


Having said that, nothing I’ve written should be construed to imply that using METHOD_NEITHER is always bad. It’s not. But in practical terms, its use in a device driver almost always is.

Peter

2 Likes

I only want to listen for a very specific kind of IOCTL (IOCTL_KS_PROPERTY)

Remember that you get literally thousands of IOCTL_KS_PROPERTY requests when a KS filter starts up. You’re going to want to be very picky about matching the property set, the property ID, and the flags (GET vs PUT vs BASICSUPPORT) before you get involved. It certainly can be done – I’ve done some very strange magic in KS filters – but it’s easy to get it wrong.

It’s also easy to get tangled up in the terminology. In that paragraph, I used the term “KS filter” twice with two very different meanings. One is “filter” in the DirectShow sense, in that every KS driver exposes a filter with pins. The other is “filter” in the PnP sense, as in a driver that inserts itself into the IRP chain.

1 Like

Thanks for the help everyone! I was able to successfully intercept the request I was interested in, access the output buffer, and make the desired changes before returning control to the application. Works like a charm as far as I can tell, but I haven’t done any thorough testing yet.

A few observations I made along the way:

@edechamps said:

@Doron_Holan said:
Both WDF_REQUEST_SEND_OPTION_SEND_AND_FORGET or WdfRequestFormatRequestUsingCurrentType from EvtIoInCallerContext will send the request down the stack in the same context (the user mode process).

Oh, nice! I didn’t know that was a possibility. That would nicely take care of the problem. I can’t find any documentation that says I can forward a request to the local I/O target from within EvtIoInCallerContext, but I can’t find any documentation that says I can’t, either…

Well, actually… I spoke too soon. I did find some documentation that says I’m not really supposed to do that:

After the callback function has finished preprocessing the request, it must either queue it by calling WdfDeviceEnqueueRequest or complete it by calling WdfRequestComplete (if an error is detected).

Note the “must”. This would seem to indicate that calling WdfRequestSend from within EvtIoInCallerContext is illegal.

That said I tested this approach and it works. I think it still makes more sense to do this in EvtIoInCallerContext than in EvtIoDeviceControl so I’m going to stick to that unless anyone has a better idea.

@Tim_Roberts said:
Filtering the KS ioctls is complicated.

If the output buffer has already been converted, it will be in the SystemBuffer field (WdfRequestWdfGetIrp(Request)->AssociatedIrp.SystemBuffer). If that is non-zero, you can use it. Otherwise, you need to call WdfRequestRetrieveUnsafeUserOutputBuffer and call WdfRequestProbeAndLockUserBufferForWrite on it.

Thank you for warning me about this. I did encounter this problem. At first I tried using the standard method for METHOD_NEITHER (i.e. using WdfRequestRetrieveUnsafeUserOutputBuffer and WdfRequestProbeAndLockUserBufferForWrite) but that didn’t work - I was getting a pointer all right, and the size was correct, but the buffer it was pointing to wasn’t being populated by the next-lower driver (in my case usbaudio.sys).

What I found confusing at first is that the IRP AssociatedIrp.SystemBuffer field wasn’t set, either. But that was because I was checking that field at the time I was receiving the request. It turns out that it was the next-lower driver that was setting that field! Therefore I had to check for it in my completion routine instead. Once I did that, I realized that was where the actual output buffer was located and at this point I was able to inspect it and make my changes.

It’s somewhat mind-boggling that a next-lower driver would suddenly decide to change the rules in the middle of an IRP and respond to a METHOD_NEITHER IOCTL with a kernel buffer. Clearly it’s giving the middle finger to the official METHOD_NEITHER rules and doing its own thing instead. I wonder how the output buffer eventually makes it back to the application then. At first I thought it was ksthunk that was patching things up but clearly that’s not the case because nothing seems to change if I remove ksthunk from the driver stack. So I guess it must be the I/O manager that notices there’s AssociatedIrp.SystemBuffer is set and copies it back to the user buffer? :confused:

It’s somewhat mind-boggling that a next-lower driver would suddenly decide to change the rules in the middle of an IRP

That suggests that your filter loaded ABOVE ksthunk instead of BELOW ksthunk, which is where you really want to be. It’s not so bad for 64-bit clients, but it’s important for 32-bit clients. You might check the UpperFilters value in the registry to see if you shouldn’t revise the order.

I wonder how the output buffer eventually makes it back to the application then.

It’s the same pages, just mapped with a different virtual address. No copies required. Whatever you write to the physical pages will be seen immediately by everyone who has the buffer mapped.

@Tim_Roberts said:

It’s somewhat mind-boggling that a next-lower driver would suddenly decide to change the rules in the middle of an IRP

That suggests that your filter loaded ABOVE ksthunk instead of BELOW ksthunk, which is where you really want to be. You might check the UpperFilters value in the registry to see if you shouldn’t revise the order.

I thought about that, yes. My driver was always loaded below ksthunk the entire time (I set it up as a device filter, and ksthunk is a class filter). When I tried loading it as a class filter above ksthunk (or without ksthunk in the stack at all) I didn’t really notice any difference, but I’ll admit I haven’t examined things too closely.

It’s not so bad for 64-bit clients, but it’s important for 32-bit clients.

I thought so too, and I was curious to see if the 32-bit version of KS Studio was affected by this. To my surprise the answer was no: x86/ksstudio.exe still works even if I remove ksthunk completely, and my driver sees no difference to how buffers are exchanged between the 32-bit and 64-bit versions (at least as far as IOCTL_KS_PROPERTY is concerned, anyway). This leaves me a bit confused as to what role ksthunk plays in this. ksthunk appears to be completely undocumented as far as I can tell. For reference, I do all my testing on the latest Windows 11 (from the stable channel).

I wonder how the output buffer eventually makes it back to the application then.

It’s the same pages, just mapped with a different virtual address. No copies required. Whatever you write to the physical pages will be seen immediately by everyone who has the buffer mapped.

That doesn’t match what I’m seeing. At the time my completion routine is called, UserBuffer and SystemBuffer are pointing to different buffers that have different contents (UserBuffer is pointing to garbage, SystemBuffer is pointing to the actual data), not just different addresses. But somehow it’s the contents of SystemBuffer that makes it back to the application, not UserBuffer. I don’t understand why that works for a METHOD_BUFFER IOCTL.

At the time my completion routine is called, UserBuffer and SystemBuffer are pointing to different buffers

Right. UserBuffer is the 1st buffer in DeviceIoControl – the “input” buffer (the KSPROPERTY structure here). SystemBuffer is the 2nd buffer – the “output” buffer (the property data here). They are different.

I don’t understand why that works for a METHOD_BUFFER IOCTL.

With METHOD_BUFFER, the first buffer is copied in and out. The second buffer is page mapped.

@Tim_Roberts said:

At the time my completion routine is called, UserBuffer and SystemBuffer are pointing to different buffers

Right. UserBuffer is the 1st buffer in DeviceIoControl – the “input” buffer (the KSPROPERTY structure here). SystemBuffer is the 2nd buffer – the “output” buffer (the property data here). They are different.

Now I’m even more confused - it feels like we’re observing completely different things. UserBuffer is definitely not the input buffer in my case. I’ve checked: the contents at that address are always garbage for the full duration of the IRP, they don’t resemble neither KSPROPERTY nor the KSMULTIPLE_ITEM output type I’m dealing with.

According to the official docs, for METHOD_NEITHER, the input buffer is Parameters.DeviceIoControl.Type3InputBuffer, the output buffer is UserBuffer, and SystemBuffer is not mentioned.

You’re saying that the input buffer is UserBuffer and the output buffer is SystemBuffer.

What I’m actually observing is that the input buffer is Parameters.DeviceIoControl.Type3InputBuffer (well, I assume that’s the case - since WdfRequestRetrieveUnsafeUserInputBuffer and WdfRequestProbeAndLockUserBufferForRead just work I haven’t looked more closely), the output buffer is SystemBuffer, and UserBuffer is unused.

So it looks like the official docs, you, and I are saying different things. I don’t know how to reconcile all that. But anyway, as long as it works…

I don’t understand why that works for a METHOD_BUFFER IOCTL.

With METHOD_BUFFER, the first buffer is copied in and out. The second buffer is page mapped.

Sorry, I misspoke - I meant “I don’t understand how that works for METHOD_NEITHER”, not METHOD_BUFFER in my previous post, of course.

@Tim_Roberts Okay so this got me intrigued, so I wrote some code that, on IRP dispatch and completion, prints the addresses of all 3 relevant pointers and attempts to “probe” them to guess what their contents look like. I then ran the driver under all possible combinations: ksthunk below and above my filter driver, as well as completely removed; with the user application being the Windows audio engine, 32-bit KS Studio, or 64-bit KS Studio - so 9 combinations in total. The next-lower driver is usbaudio.sys.

What I found was all 9 combinations, except one, produced something similar to the following:

WinSoftVol: got KS nodes property get request:
WinSoftVol: AssociatedIrp.SystemBuffer = 0x0000000000000000 [(null)]
WinSoftVol: UserBuffer = 0x0000017AC15412F0 [unknown/garbage]
WinSoftVol: Parameters.DeviceIoControl.Type3InputBuffer = 0x000000451E37F400 [KSPROPERTY topology nodes get request]
WinSoftVol: in completion routine:
WinSoftVol: AssociatedIrp.SystemBuffer = 0xFFFFD4881C1EE9C0 [KSMULTIPLE_ITEM response with GUIDs]
WinSoftVol: UserBuffer = 0x0000017AC15412F0 [unknown/garbage]
WinSoftVol: Parameters.DeviceIoControl.Type3InputBuffer = 0x000000451E37F400 [KSPROPERTY topology nodes get request]

The only exception is when ksthunk is above my filter driver and 32-bit KS Studio is used:

WinSoftVol: got KS nodes property get request:
WinSoftVol: AssociatedIrp.SystemBuffer = 0xFFFFD4881BEBEEC0 [unknown/garbage]
WinSoftVol: UserBuffer = 0x0000000002EFDD08 [unknown/garbage]
WinSoftVol: Parameters.DeviceIoControl.Type3InputBuffer = 0x0000000002F266F0 [KSPROPERTY topology nodes get request]
WinSoftVol: in completion routine:
WinSoftVol: AssociatedIrp.SystemBuffer = 0xFFFFD4881BEBEEC0 [KSMULTIPLE_ITEM response with GUIDs]
WinSoftVol: UserBuffer = 0x0000000002EFDD08 [unknown/garbage]
WinSoftVol: Parameters.DeviceIoControl.Type3InputBuffer = 0x0000000002F266F0 [KSPROPERTY topology nodes get request]

My conclusions are as follows:

  • The next-lower driver, usbaudio.sys, is using Type3InputBuffer as the input buffer, consistent with the official documentation.
  • However when it comes to the output buffer, the next-lower driver is clearly deviating from the official METHOD_NEITHER rules. The rules state the output buffer is UserBuffer, but in practice the driver seems to be ignoring that field. The driver is using SystemBuffer as the output buffer instead.
  • In almost all cases, SystemBuffer is NULL when the IRP is dispatched. It is the next-lower driver that is setting SystemBuffer before completing the IRP. The only exception is if ksthunk sits on top and a 32-bit user application is used; in this case, SystemBuffer is already allocated when the IRP is dispatched.

I also observed that, somewhat surprisingly, as far as the user application is concerned, things still appear to work even if ksthunk is completely removed from the driver stack. That includes even 32-bit KS Studio. So whatever ksthunk is doing, it doesn’t appear to be strictly necessary for proper operation. At least as far as KS property queries are concerned, that is.

Obviously it’s quite possible that some of the above conclusions only hold for specific IOCTLs and/or user-mode applications and/or specific next-lower drivers and/or specific Windows versions. I guess only testing will tell.

There are many weird things in the KS communication path. When an audio device is opened, the file name in the IRP_MJ_CREATE call includes the WAVEFORMATEX structure that defines the client’s desired format. No one promised that file names would be simple zero-terminated strings, but finding a binary structure in there was a surprise.

1 Like