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/


Help converting a WDM KS (camera) filter driver to KMDF

Paul_JacksonPaul_Jackson Member Posts: 32
Hi guys,

After the latest thread, I decided to convert the WDM driver I have to KMDF. I have a filter driver which captures the IOCTL_KS_READ_STREAM control code, and sets a completion routine with `WdfRequestSetCompletionRoutine` to post-process a camera's image.

I try to access the image structure and the frame in the callback, but unfortunately I weren't able to do this using KMDF API. In the WDM API, I accessed the following:
(PKSSTREAM_HEADER)Irp->AssociatedIrp.SystemBuffer
MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority)

In the KMDF callback, I tried using these APIs:
WdfRequestRetrieveUnsafeUserInputBuffer
WdfRequestProbeAndLockUserBufferForRead

But that doesn't work for me.
Could you suggest a way to access and modify the frame data? Thanks.

Comments

  • Tim_RobertsTim_Roberts Member - All Emails Posts: 14,563
    [email protected] wrote:
    > After the latest thread, I decided to convert the WDM driver I have to KMDF. I have a filter driver which captures the IOCTL_KS_READ_STREAM control code, and sets a completion routine with `WdfRequestSetCompletionRoutine` to post-process a camera's image.
    >
    > I try to access the image structure and the frame in the callback, but unfortunately I weren't able to do this using KMDF API. In the WDM API, I accessed the following:
    > (PKSSTREAM_HEADER)Irp->AssociatedIrp.SystemBuffer
    > MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority)
    >
    > In the KMDF callback, I tried using these APIs:
    > WdfRequestRetrieveUnsafeUserInputBuffer
    > WdfRequestProbeAndLockUserBufferForRead
    >
    > But that doesn't work for me.
    > Could you suggest a way to access and modify the frame data? Thanks.

    You have embarked on a non-trivial task.  The issue is that all of the
    IOCTL_KS_* ioctls were designed for kernel-to-kernel communication. 
    They are METHOD_NEITHER, and they don't follow any of the defined IRP
    rules, which means you can't really use the KMDF APIs to access the
    data.  The rules they do follow are undocumented.  What I know, I know
    from reverse engineering.

    In IOCTL_KS_READ_STREAM, the Type3InputBuffer and SystemBuffer are not
    used.  The address of the KSSTREAM_HEADER is in UserBuffer, and the
    MdlAddress points to the actual frame buffer (not the KSSTREAM_HEADER).

    However, some of this is set up by ksthunk.sys, which sits in between
    64-bit KS drivers and 32-bit apps.  The setup may be slightly different
    on a 32-bit system, where ksthunk doesn't apply.

    --
    Tim Roberts, [email protected]
    Providenza & Boekelheide, Inc.

    Tim Roberts, [email protected]
    Providenza & Boekelheide, Inc.

  • Paul_JacksonPaul_Jackson Member Posts: 32
    Thank you for the reply Tim, and for the various posts in the forums with information about KS. Your posts gave me some insight about what's going on.

    Your information helped me understand the following code on GitHub, which seems to work:
    https://github.com/flowerinthenight/windows-camera-class-filter-driver/blob/master/ccfltr/ccfltr.c#L395

    Now I'm trying to upgrade the WDM example to KMDF. I started with the toaster KMDF example, and tried to use `WdfRequestSetCompletionRoutine` for post-processing:
    https://github.com/Microsoft/Windows-driver-samples/blob/master/general/toaster/toastDrv/kmdf/filter/generic/filter.c#L328

    Your comment, "you can't really use the KMDF APIs to access the data", made me notice that I can use the `WdfRequestWdmGetIrp` function to get the raw IRP pointer in the completion routine, and use a code which is similar to the WDM example. So far, looks like it works.

    Are the any other details that I need to be aware of?

    Also, you mentioned WdfSynchronizationScopeNone in one of the threads:
    https://www.osronline.com/showthread.cfm?link=212301
    Can you please say whether and why it's needed in my case?

    Thank you so much for the help!
  • Peter_Viscarola_(OSR)Peter_Viscarola_(OSR) Administrator Posts: 9,077
    Hmmmm... In reference to the (almost 7 year old) thread you linked: If you're processing METHOD_NEITHER IOCTLs, and you want to be in the context of the current process, relying on Sync Scope None is a poor practice. It works by way of implementation, not by way of architectural correctness.

    If you want to be called in the context of the requesting process, you should use the InCallerContext Event Processing Callback.

    Are you RECEIVING MTHOD_NEITHER IOCTLs or are you SENDING them? Sorry... I'm not a Ks guy, so I can only be of limited help with that end of things. But I'm reasonably familiar with WDF.

    Peter
    OSR
    @OSRDrivers

    Peter Viscarola
    OSR
    @OSRDrivers

  • Tim_RobertsTim_Roberts Member - All Emails Posts: 14,563
    [email protected] wrote:
    > Hmmmm... In reference to the (almost 7 year old) thread you linked: If you're processing METHOD_NEITHER IOCTLs, and you want to be in the context of the current process, relying on Sync Scope None is a poor practice. It works by way of implementation, not by way of architectural correctness.
    >
    > If you want to be called in the context of the requesting process, you should use the InCallerContext Event Processing Callback.
    >
    > Are you RECEIVING MTHOD_NEITHER IOCTLs or are you SENDING them? Sorry... I'm not a Ks guy, so I can only be of limited help with that end of things. But I'm reasonably familiar with WDF.

    Receiving.  It is the KS subsystem that sends these ioctls to streaming
    drivers.

    The fact that these ioctls don't follow the rules makes the usual "in
    caller context" advice somewhat squishy.  Although these are
    METHOD_NEITHER ioctls, they originate in kernel mode, and they do not
    follow the rules of a METHOD_NEITHER ioctl that you would get from user
    mode.  The address fields in the IRP are used in a way that is specific
    to KS, and all the addresses have already been converted to kernel
    addresses.

    There is some truly weird voodoo magic in the KS interface.  They open a
    file handle to the filter, and a file handle to each pin.  When you get
    the IRP_MJ_CREATE call for the pin, the file name in the request not
    only includes the device interface reference string, but also the
    desired video format, as a binary structure, including embedded zeros. 
    That works just fine, as long as the driver knows what it is going to
    receive, but it's certainly odd.

    --
    Tim Roberts, [email protected]
    Providenza & Boekelheide, Inc.

    Tim Roberts, [email protected]
    Providenza & Boekelheide, Inc.

  • Tim_RobertsTim_Roberts Member - All Emails Posts: 14,563
    [email protected] wrote:
    > Your information helped me understand the following code on GitHub, which seems to work:
    > https://github.com/flowerinthenight/windows-camera-class-filter-driver/blob/master/ccfltr/ccfltr.c#L395

    Maybe, but I'm suspicious of anyone who writes code like this:

    InterlockedAnd(&pDeviceExtension->IsVideoInfoHeader2, 0);
    InterlockedAnd(&pDeviceExtension->KsStateRun, 0);
    InterlockedAnd(&pDeviceExtension->StreamerPid, 0);

    That shows a misunderstanding on several levels.  A DeviceExtension is
    automatically zeroed on creation, and a write of a 32-bit value is
    already atomic, so this would have had the same effect:

    pDeviceExtension->IsVideoInfoHeader2 = 0;
    pDeviceExtension->KsStateRun = 0;
    pDeviceExtension->StreamerPid = 0;


    > Your comment, "you can't really use the KMDF APIs to access the data", made me notice that I can use the `WdfRequestWdmGetIrp` function to get the raw IRP pointer in the completion routine, and use a code which is similar to the WDM example. So far, looks like it works.

    Yep, that's the right way to do it.

     
    > Are the any other details that I need to be aware of?

    I'm sure there are lots, but it depends on what you need to do.  If you
    need to know the format of the video stream as it was negotiated, that
    turns out to be complicated.  Are you able to assume a format?


    > Also, you mentioned WdfSynchronizationScopeNone in one of the threads:
    > https://www.osronline.com/showthread.cfm?link=212301
    > Can you please say whether and why it's needed in my case?

    It's difficult to come up with general rules.  You need to know what
    parts of your shared state are vulnerable and need locking.  If you
    access a lot of shared data, then a sync scope can make that easier, but
    it's also easy to overdo it.

    --
    Tim Roberts, [email protected]
    Providenza & Boekelheide, Inc.

    Tim Roberts, [email protected]
    Providenza & Boekelheide, Inc.

  • Paul_JacksonPaul_Jackson Member Posts: 32
    > I'm suspicious of anyone who writes code like this

    Maybe it's not perfect, but that's literally the only relevant example that I found in the internet.

    > a write of a 32-bit value is already atomic

    That's probably true in practice for all relevant processors, but isn't it true that in theory some processor (e.g. some esoteric ARM) can lack such guarantees?

    > If you need to know the format of the video stream as it was negotiated, that turns out to be complicated.

    The author of the example does this by checking the GUID at VideoInfoHeader.DataFormat.SubFormat. Is there more to it?

    My task is to replace the webcam image if a condition is met, as a security measure. I can have a replacement image for common formats (say MJPG and a couple of more), and just have a zero-sized buffer returned for less known formats, which is acceptable as long as the streaming is blocked.
  • Peter_Viscarola_(OSR)Peter_Viscarola_(OSR) Administrator Posts: 9,077
    <quote>
    The fact that these ioctls don't follow the rules makes the usual "in
    caller context" advice somewhat squishy. Although these are
    METHOD_NEITHER ioctls, they originate in kernel mode, and they do not
    follow the rules of a METHOD_NEITHER ioctl that you would get from user
    mode. The address fields in the IRP are used in a way that is specific
    to KS, and all the addresses have already been converted to kernel
    addresses.
    </quote>

    That makes sense. You definitely know KS.

    The only other thing I'll throw in here in CASE it's useful, is that KMDF "knows about" kernel-mode to kernel-mode METHOD_NEITHER IOCTLs. And, IIRC, actually supports the use of the typical WdfRequestRetrieveOutputBuffer/WdfRequestRetrieveInputBuffer functions in this case.

    Assuming the buffer pointers are KVAs, of course, and the lengths are... proper.

    <quote>
    Maybe it's not perfect, but that's literally the only relevant example that I
    found in the internet.
    </quote>

    Confucius once wrote: "Posting a sample on the Internet doesn't make the author wise or the sample good." I think he wrote this in Autumn Annals, just after The September That Never Ended.

    <quote>
    That's probably true in practice for all relevant processors, but isn't it true
    that in theory some processor (e.g. some esoteric ARM) can lack such guarantees?
    </quote>

    Seriously?

    Allow me to put your fears to rest: No system on which Windows will run will violate this constraint.

    (This reminds me of the arguments people used to give me about assuming endian-ness in my network driver code: "Suppose you're running on a big endian system," they would say. My answer was always "Then my driver isn't running at all, because Windows isn't going to run on a big endian system anytime soon.")

    Peter
    OSR
    @OSRDrivers

    Peter Viscarola
    OSR
    @OSRDrivers

  • Tim_RobertsTim_Roberts Member - All Emails Posts: 14,563
    [email protected] wrote:
    > (This reminds me of the arguments people used to give me about assuming endian-ness in my network driver code: "Suppose you're running on a big endian system," they would say. My answer was always "Then my driver isn't running at all, because Windows isn't going to run on a big endian system anytime soon.")

    Exactly, and this applies across the board -- even to Windows CE. 
    Although CE runs on many strange processors that are traditionally
    big-endian, the operating system requires them to run in little-endian mode.

    --
    Tim Roberts, [email protected]
    Providenza & Boekelheide, Inc.

    Tim Roberts, [email protected]
    Providenza & Boekelheide, Inc.

  • Paul_JacksonPaul_Jackson Member Posts: 32
    OK, good to know.

    Another question about cameras: can IRP_MJ_CREATE be called for the same camera device object more than once, before the previous handle gets IRP_MJ_CLEANUP? Also, is there any significance to IRP_MJ_CLOSE, or does it always happen right after IRP_MJ_CLEANUP? Which one is better to consider the camera activity to be stopped?
  • Tim_RobertsTim_Roberts Member - All Emails Posts: 14,563
    On Apr 22, 2018, at 10:44 AM, [email protected] <[email protected]> wrote:
    >
    > OK, good to know.
    >
    > Another question about cameras: can IRP_MJ_CREATE be called for the same camera device object more than once, before the previous handle gets IRP_MJ_CLEANUP?

    Certainly. A single camera can be opened by multiple processes, or multiple times by a single process. In the KS world, for example, you'll get separate file opens for the filter and for each of the pins.


    > Also, is there any significance to IRP_MJ_CLOSE, or does it always happen right after IRP_MJ_CLEANUP? Which one is better to consider the camera activity to be stopped?

    For a KS driver, neither one. Camera activity begins when the pin transitions to the run state (KSSTATE_RUN), and stops when the pin transitions out of run state. A KS driver's streaming state is controlled by the KSPROPERTY_CONNECTION_STATE property within KSPROPSETID_Connection.

    IRP_MJ_CLEANUP happens while the process is still accessible. IRP_MJ_CLOSE can arrive after the process memory is gone. The OSR web site has an extensive article on why it's better to handle IRP_MJ_CLOSE.

    Tim Roberts, [email protected]
    Providenza & Boekelheide, Inc.

    Tim Roberts, [email protected]
    Providenza & Boekelheide, Inc.

  • Paul_JacksonPaul_Jackson Member Posts: 32
    I don't quite understand how it works.

    > A single camera can be opened by multiple processes, or multiple times by a single process.

    I know that only one process at a time can use a given camera. How is this enforced? Where does the second connection fail? I tried debugging the filter driver, but didn't find clues.

    I see that a regular communication for a single process looks like this:
    =====================================
    IRP_MJ_CREATE (filename = "\global")
    IRP_MJ_CREATE (filename = "{146F1A80-4791-11D0-A5D6-28DB04C10000}\<binary_data>")
    KSSTATE_ACQUIRE
    KSSTATE_PAUSE
    KSSTATE_RUN
    IOCTL_KS_READ_STREAM
    IOCTL_KS_READ_STREAM
    ...
    KSSTATE_PAUSE
    KSSTATE_STOP
    IRP_MJ_CLEANUP
    IRP_MJ_CLOSE
    IRP_MJ_CLEANUP
    IRP_MJ_CLOSE
    =====================================

    Now, if I open another app while already using the camera, it looks like this:
    =====================================
    IRP_MJ_CREATE(1) (filename = "\global")
    IRP_MJ_CREATE(1) (filename = "{146F1A80-4791-11D0-A5D6-28DB04C10000}\<binary_data>")
    KSSTATE_ACQUIRE(1)
    KSSTATE_PAUSE(1)
    KSSTATE_RUN(1)
    IOCTL_KS_READ_STREAM(1)
    IOCTL_KS_READ_STREAM(1)
    ...
    IRP_MJ_CREATE(2) (filename = "\global")
    IRP_MJ_CREATE(2) (filename = "{146F1A80-4791-11D0-A5D6-28DB04C10000}\<binary_data>")
    KSSTATE_ACQUIRE(2)
    KSSTATE_PAUSE(2)
    KSSTATE_RUN(2)
    IOCTL_KS_READ_STREAM(1)
    ...
    KSSTATE_PAUSE(1)
    KSSTATE_STOP(1)
    IRP_MJ_CLEANUP(1)
    IRP_MJ_CLOSE(1)
    IRP_MJ_CLEANUP(1)
    IRP_MJ_CLOSE(1)
    =====================================

    I'd like to be able to connect the video format in <binary_data> to IOCTL_KS_READ_STREAM, to be able to know what to expect in the frame buffer. But in order for this to work correctly, I need to know to discard the "IRP_MJ_CREATE(2)" IRP.
  • Tim_RobertsTim_Roberts Member - All Emails Posts: 14,563
    [email protected] wrote:
    > I don't quite understand how it works.
    >
    >> A single camera can be opened by multiple processes, or multiple times by a single process.
    > I know that only one process at a time can use a given camera. How is this enforced? Where does the second connection fail? I tried debugging the filter driver, but didn't find clues.

    At the transition to KSSTATE_ACQUIRE, the driver is supposed to fail
    that if the hardware resources are already committed.  This convention
    was invented during the days of Windows Media Center, because it opened
    an instance in a service at boot time, even if it wasn't recording
    anything.  If drivers failed during Create, no other devices could use
    the camera.


    > I see that a regular communication for a single process looks like this:
    > =====================================
    > IRP_MJ_CREATE (filename = "\global")

    That's the filter.

     
    > IRP_MJ_CREATE (filename = "{146F1A80-4791-11D0-A5D6-28DB04C10000}\<binary_data>")

    That's the pin.  The <binary data> is the selected video format.


    > Now, if I open another app while already using the camera, it looks like this:
    > =====================================
    > IRP_MJ_CREATE(1) (filename = "\global")
    > IRP_MJ_CREATE(1) (filename = "{146F1A80-4791-11D0-A5D6-28DB04C10000}\<binary_data>")
    > KSSTATE_ACQUIRE(1)
    > KSSTATE_PAUSE(1)
    > KSSTATE_RUN(1)
    > IOCTL_KS_READ_STREAM(1)
    > IOCTL_KS_READ_STREAM(1)
    > ...
    > IRP_MJ_CREATE(2) (filename = "\global")
    > IRP_MJ_CREATE(2) (filename = "{146F1A80-4791-11D0-A5D6-28DB04C10000}\<binary_data>")
    > KSSTATE_ACQUIRE(2)

    That should have failed.


    > I'd like to be able to connect the video format in <binary_data> to IOCTL_KS_READ_STREAM, to be able to know what to expect in the frame buffer. But in order for this to work correctly, I need to know to discard the "IRP_MJ_CREATE(2)" IRP.

    You get a file handle in the IOCTL_KS_READ_STREAM call.  You can connect
    that to the corresponding IRP_MJ_CREATE.

    --
    Tim Roberts, [email protected]
    Providenza & Boekelheide, Inc.

    Tim Roberts, [email protected]
    Providenza & Boekelheide, Inc.

  • Paul_JacksonPaul_Jackson Member Posts: 32
    You are right, KSSTATE_ACQUIRE fails with 0xc000009a (STATUS_INSUFFICIENT_RESOURCES). Here's why I didn't see it:
    https://github.com/flowerinthenight/windows-camera-class-filter-driver/issues/2

    Another question:
    In the completion routine for IOCTL_KS_READ_STREAM, the example uses ProbeForRead on KSSTREAM_HEADER's Data pointer. That doesn't look correct to me, but I'm not sure. Driver Verifier says:
    Error String ProbeForRead should only be called at IRQL <= APC_LEVEL.

    I think that there are two issues here: first the IRQL mismatch, and second the fact that the Data pointer can be irrelevant since the completion routine is likely to run in the context of a different process. Am I right? And if that's so, can this check be just removed? I mean, can I trust Irp->MdlAddress without additional checks? Or can it be invalid, and if so what's the proper way to check this?

    Thanks again for your help!
  • Tim_RobertsTim_Roberts Member - All Emails Posts: 14,563
    [email protected] wrote:
    > Another question:
    > In the completion routine for IOCTL_KS_READ_STREAM, the example uses ProbeForRead on KSSTREAM_HEADER's Data pointer. That doesn't look correct to me, but I'm not sure. Driver Verifier says:
    > Error String ProbeForRead should only be called at IRQL <= APC_LEVEL.

    That shouldn't be necessary.  Maybe this was an issue in Windows 98, but
    these days all the addresses in the IRP should be kernel-mode addresses
    by the time they get to you.  There's a filter called "ksthunk" that
    sits on top of the main driver and provides a nice, clean IRP to the
    drivers below it.  That way, lower drivers don't have to deal with the
    32/64 issues.

    How far back do you need to support?  Are you worried about XP?

     
    > I think that there are two issues here: first the IRQL mismatch, and second the fact that the Data pointer can be irrelevant since the completion routine is likely to run in the context of a different process. Am I right? And if that's so, can this check be just removed? I mean, can I trust Irp->MdlAddress without additional checks? Or can it be invalid, and if so what's the proper way to check this?

    The Irp->MdlAddress buffer should already be locked by the time you see it.

    --
    Tim Roberts, [email protected]
    Providenza & Boekelheide, Inc.

    Tim Roberts, [email protected]
    Providenza & Boekelheide, Inc.

  • Paul_JacksonPaul_Jackson Member Posts: 32
    Great, thanks.

    > How far back do you need to support? Are you worried about XP?

    Windows 7 and up should be supported. Windows XP is nice to have if I can get the support for free or almost for free.
    What's the difference regarding Windows XP?
  • Tim_RobertsTim_Roberts Member - All Emails Posts: 14,563
    [email protected] wrote:
    >
    >> How far back do you need to support? Are you worried about XP?
    > Windows 7 and up should be supported. Windows XP is nice to have if I can get the support for free or almost for free.
    > What's the difference regarding Windows XP?

    The "ksthunk" helper I mentioned came into being when the 64-bit systems
    arrived.  In the XP days, it's quite possible you WILL have to worry
    about locking and mapping user-mode pointers.  That's ugly.

    --
    Tim Roberts, [email protected]
    Providenza & Boekelheide, Inc.

    Tim Roberts, [email protected]
    Providenza & Boekelheide, Inc.

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!
Internals & Software Drivers 19-23 June 2023 Live, Online
Writing WDF Drivers 10-14 July 2023 Live, Online
Kernel Debugging 16-20 October 2023 Live, Online
Developing Minifilters 13-17 November 2023 Live, Online