OSRLogoOSRLogoOSRLogo x OSR Custom Development Services

Everything Windows Driver Development

GoToHomePage xLoginx

    Thu, 14 Mar 2019     118020 members


  Online Dump Analyzer
OSR Dev Blog
The NT Insider
The Basics
File Systems
ListServer / Forum
  Express Links
  · The NT Insider Digital Edition - May-June 2016 Now Available!
  · Windows 8.1 Update: VS Express Now Supported
  · HCK Client install on Windows N versions
  · There's a WDFSTRING?
  · When CAN You Call WdfIoQueueP...ously

Multi-Version Functionality

 Click Here to Download: Code Associated With This Article Zip Archive, 7KB

You start writing your driver, carefully including support for multiple O/S versions. You discover the perfect DDK or IFS kit function that solves one of your key problems. You’re happy! But, in the software world you don’t always get to live happily ever after: You discover that this perfect function isn’t available on all the versions of the O/S that you need to support! Arrgh!

Well this is exactly what happened recently here at OSR when we tried to use IoCreateFileSpecifyDeviceObjectHint for a file system filter driver that needed to run on Windows 2000, Windows XP, and Windows Server 2003. This article discusses our solution to implementing this functionality on Windows 2000, while allowing us to use the native functionality on Windows XP and Windows Server 2003.

Problem Scenario

Let’s start by explaining the problem. We were writing a file system filter driver for insertion into the storage stack above a file system. By default, we knew that our filter may be one of a number of filters attached to the file system. The specific issue arose when our filter needed to open or create a file on the file system we were filtering. In order to do the open, in Pre Windows XP systems, we either had to use ZwCreateFile or attempt to build our own Create IRP. Each of these 2 methods had problems, they were:

  • ZwCreateFile - If this was used, then the Create IRP that was generated would start at the stop of the storage stack and eventually reenter our driver. This would result in a lot of stack space being used, recursion, and reentrancy problems.
  • Building your own Create IRP - Well, it’s just way too hard and not documented at all so it was not an option.

The introduction of IoCreateFileSpecifyDeviceObjectHint in Windows XP solved the problem. Specifically,

"The IoCreateFileSpecifyDeviceObjectHint routine is used by file system filter drivers to send a create request only to the filters below a specified device object and to the file system. Filters that are attached above the specified device object in the driver stack do not receive the create request." (Windows Server 2003 IFS Kit Documentation).

Great...except that this solution only addresses support for Windows XP/Server 2003. Given we need to implement this functionality in Windows 2000 as well, we sought to mimic the functionality of IoCreateFileSpecifyDeviceObjectHint on Win2K. So what do we do?

Operating Environment

If you look below at Figure 1, assume that the filter driver that we are implementing is OSR Filter B. Our filter has created Device Object B and it has been attached to Device Object C that was created by Filter C (loaded before us). Likewise Filter A (loaded after us) and its Device Object A has been attached to our Device Object B. What we need is some way to build a Create IRP and have that IRP delivered to Filter C without having the IRP traverse any filters attached above it (i.e., Filter A).

Figure 1 - Attached Filter Drivers

What Do We Need to Know?

We know that the I/O Manager and Object Manager work together to parse names to determine the Device Object that is the eventual target of a Create request. So, for example, if \\??\\C:\Fred.Txt (or \\Global??\\C:\Fred.Txt on XP) was specified in a Create request, as part of parsing this name string the I/O Manager and Object Manager might determine that the Device Object named "\Device\HarddiskVolume1" was the target for operations on the logical device named "C:". Thus, the I/O Manager would build a Create IRP (containing the remainder of the file name, "\fred.txt" in our example) and pass it to the driver that’s responsible for processing requests for the "\Device\HarddiskVolume1" device.

Further, we know if we create a Device Object with a unique name we can get the I/O Manager to deliver Create requests with that name to our Device Object so that we can process the create.

Finally, we know we can still use ZwCreateFile to build a Create IRP.

So we just have to pool what we know in order to get Create Requests delivered to the correct place.

Putting It Together

Okay, so recall that we want OSR Filter B to build a Create request and have that request sent to Filter C without having it reenter the top of the stack. So, let’s say that our filter creates a named Device Object, as shown in Figure 2, in addition to the one that it created and attached to Filter C. Whenever OSR Filter B needs to do a Create, it does a ZwCreateFile to the named Device Object.

Figure 2 - Attached Filter Drivers with Named Device Object

Here it is in more detail. Let’s say that our stack of filters is filtering drive "C:" and a Win32 user does a CreateFile request to open up "C:\FRED.TXT". When OSR Filter B receives the request, the name contained within the File Object is "\FRED.TXT". Now OSR Filter B wants to open up the file and do something with it before it allows the Win32 user’s Create to continue. What OSR Filter B would do is issue a ZwCreateFile request specifying the name "\Device\OurUniqueDeviceName\Fred.Txt". This would result in a IRP_MJ_CREATE being passed to the named Device Object that we created in OSR Filter B.

When OSR Filter B gets an IRP_MJ_CREATE request, it needs to look at the Device Extension that we have created for our Device Object. Specifically, it needs to look for a bit that we set when we created the Device Extension that indicates that this is our Named Device Object as opposed to our regular unnamed Device Object. If the bit is not set, then we know that this is just a normal Create that came down the top of the stack. If the bit is set however, then we know that this request is one that we built for ourselves and all we need to do is pass it on to Filter C by calling IoCallDriver with the address of "Filter C’s" Device Object (which we stored in our named Device Object’s Device Extension when we created it). Pretty simple don’t you think?

The Devil is in the Details

Yeah, well it always is. The code (Please see zip archive reference at top) mimics the functionality of IoCreateFileSpecifyDeviceObjectHint for Windows 2000 support. As you will notice, there is a compile time define "NT_XP" that our "Sources" file sets to 1 if this is an Windows XP or later build. You will also see that the Device Name that we use for the named Device Object is "\Device\XXXXXXXX", where XXXXXXXX is the UNICODE address of the Device Object that we eventually want to send the Create IRP to.

So There You Have It

When attempting to solve a problem on multiple Windows platforms, you have to get creative. Windows provides the toolset, you just have to supply the brains to know how to use them to your advantage.

Related Articles
Caching in Network File Systems
File System Filter Context - Observations and Comments

User Comments
Rate this article and give us feedback. Do you find anything missing? Share your opinion with the community!
Post Your Comment

What is the right way to close a handle opened with this techinque? If we do some processing in IRP_MJ_CLOSE procedure how to avoid re-entrancy in ZwClose?

24-Oct-09, Oleg Zonov

And what about ZwClose? How should we close such a file? Suppose we must do something in our IRP_MJ_CLOSE routine. How would we differentiate between our internal ZwClose request and win32 CloseHandle request? Could we use IoGetTopLevelIrp/IoSetTopLevelIrp for that purpose?

12-Oct-09, Oleg Zonov

"NTFS assertions!"
How do you avoid the following Ntfs assertion about the File object created against the named device object (not attached to the volume device stack) having a NULL Vpb, and not corresponding to the stack you just switched to? I found that if you do set the FileObject's device object to be that for the volume you are switching to you fix the assert raised by NTFS (assert is below). This means keeping the DeviceObject ReferenceCount field correct though. (It is incremented on a CREATE and decremented on CLOSE for the file object.)

Thanks Steve

// *** Assertion failed: No correspondence btwn file and device in irp ((pIrpSp->FileObject->Vpb == NULL) && ((pIrpSp->FileObject->DeviceObject != NULL) && (pIrpSp->FileObject->DeviceObject->Vpb != NULL) && (pIrpSp->DeviceObject == pIrpSp->FileObject->DeviceObject->Vpb->DeviceObject))) || PA((pIrpSp->FileObject->Vpb != NULL) && (pIrpSp->DeviceObject == pIrpSp->FileObject->Vpb->DeviceObject)) // *** Source File: d:\xpsprtm\base\fs\ntfs\strucsup.c, line 6621

27-Sep-04, Steve Goddard

"Code associated with the article"
*Code Associated With This Article Will Be Posted Shortly*

When will the code be posted?

26-Jul-04, Razvan Hobeanu

"Fixing the comment form"
>> By the way, is it possible to make the field for comments in this form wider?

Thanks for the suggestion -- It has been done.

29-Aug-03, Peter Viscarola

"DeviceObject address is unique"
While I agree the the GUID is a nice idea, the Device Object address is unique enough and since the name is deleted when the device object is deleted, there should be no collision problems.

28-Aug-03, Mark Cariddi

Post Your Comments.
Print this article.
Email this article.
bottom nav links