Still Feeling Insecure? - IoCreateDeviceSecure( ) for Windows 2K/XP/.NET
The NT Insider, Vol 9, Issue 4, Jul-Aug 2002 | Published: 15-Aug-02| Modified: 11-Oct-02
A couple of months back, we talked about how you use an INF file to add security to Device Objects created by your driver and attached to your Device Stack on Win2K, XP, or .NET (Inside Driver & Device Security, The NT Insider, March-April 2002). Of course, this sort of begs response to a couple of questions: How do you apply security to any named Device Objects that your driver might create in addition to the one(s) it attaches to its Device Stack? How do you set default security on a PDO? And, what do you do if you have a driver that’s not PnP compliant or (heaven forbid) not installed via an INF?
Well, it seems that Microsoft has given us an answer to these questions by creating a new DDI: IoCreateDevice Secure. This function is part of a trend in which some functionality provided in new versions of the DDK is implemented by code in a static library, and is thus usable all the way back to Windows 2000®. The first feature made available this way was cancel safe IRP queuing (the IoCsqXxx functions). Now IoCreateDevice Secure is another such function. The definition for IoCreateDeviceSecure appears in the header “wdmsec.h” (which makes for pretty interesting reading, by the way) ; Your driver links with “wdmsec.lib” to include the base code that implements this function.
IoCreateDeviceSecure is designed to be used by:
- Drivers that create named Device Objects that aren’t part of a PnP stack (what are often called “legacy Device Objects”). Such a driver might be an NT V4 style driver that creates Device Objects in its DriverEntry entry point, or it might be a PnP driver that creates an additional named Device Object (for use as a driver-wide management interface, for example) in its AddDevice entry point.
- Bus drivers that create PDOs and want to set default security in case the INF specifying a function driver for the PDO doesn’t supply any. This is especially important for PDOs that can be used directly, without a Function Driver or INF (so called “Raw PDOs”).
The prototype IoCreateDeviceSecure for the function is shown in Figure 1. Not surprisingly, it looks almost identical to the prototype for IoCreateDevice. The only difference between the two functions is the addition of the DefaultSDDLString and DeviceClassGuid parameters to IoCreateDeviceSecure.
IN PDRIVER_OBJECT DriverObject,
IN ULONG DeviceExtensionSize,
IN PUNICODE_STRING DeviceName OPTIONAL,
IN DEVICE_TYPE DeviceType,
IN ULONG DeviceCharacteristics,
IN BOOLEAN Exclusive,
IN PUNICODE_STRING DefaultSDDLString,
IN LPCGUID DeviceClassGuid,
OUT PDEVICE_OBJECT * DeviceObject);
Figure 1 — Prototype: IoCreateDeviceSecure
Name That Security
The DefaultSDDLString parameter allows you to specify a string that describes the security to be applied to your device object in Security Descriptor Definition Language (SDDL, documented in the SDK). We talked about SDDL in our previous article mentioned above so I’ll spare you the details. One thing that you need to know, however, is that while you can use every possible SDDL feature to define a security descriptor via an INF file, IoCreateDeviceSecure only supports a subset of SDDL features. This sub-set is tailored to support SDDL features that are both most commonly used and relevant to Device Object protection. So, for example, when you call IoCreateDeviceSecure you can specify the built-in administrators group using the string “BA”, but not the SID specification “S-1–5-32-544”. No loss here – it’s not like anybody knows that SID thing, right? So, for example, you would create a UNICODE_STRING containing your SDDL strings as shown in
Figure 2 — Defining a Security Descriptor
Specifying protection strings can sometimes get complex, especially if you have a device that implements a name space. However, for most devices simple protection schemes will do. To make it easier to specify commonly used sets of Device Object protection, “wdmsec.h” contains a set of predefined SDDL values stored in UNICODE_STRING that you can refer to by name. These names are pretty much self-explanatory. A couple of the more useful definitions are:
SDDL_DEVOBJ_SYS_ALL_ADMIN_ALL — Specifies that both the system and administrators have all access to the device
SDDL_DEVOBJ_SYS_ALL_ADM_RWX_WORLD_R — Allows the system and admins all access – though by default admins won’t be able to change the protection of the device without explicitly taking ownership of it first – and allows only read access for everyone else.
Using one of these symbolic names makes calling IoCreateDeviceSecure pretty simple (shown in
code = IoCreateDeviceSecure(DriverObject,
Figure 3 — Calling IoCreateDeviceSecure
What's That GUID For?
The other parameter that distinguishes IoCreateDeviceSecure from its older brother, is the DeviceClassGuid parameter. The value passed here can be used to associate the newly created Device Object with a specific device setup class. As we described in the previous article, an INF file can specify a security descriptor as well as device type and device characteristics for all devices in a given device setup class. Specifying the GUID on IoCreateDeviceSecure allows the system to look up these defaults and use them to override those provided on the call to IoCreateDeviceSecure. Did you hear that? Any values stored in the Registry (such as those provided in an INF) that specify per device security, or specify security for an entire device class will take precedence over those specified by IoCreateDeviceSecure.
The DeviceClassGuid parameter is also used if no information is found in the Registry. In this case, the system uses the DeviceClassGuid as a key under which to store the security descriptor specified by IoCreateDeviceSecure. The descriptor is marked in such a way to ensure that updating the driver binary will properly update the default descriptor stored in the Registry. In addition, if the class in the INF matches the DeviceClassGuid passed to IoCreateDeviceSecure, PnP will update the entire stack’s security - including the PDOs.
So what do you specify as DeviceClassGuid when you call IoCreateDeviceSecure in your driver? Well, drivers that support PnP (like a bus driver that is using IoCreateDeviceSecure to create a Raw PDO) typically provide their actual device setup class GUID for this parameter. Other drivers, such as NT V4 style drivers can just spin a new GUID and use that.
How to Use It
Let’s look at an example. Consider a driver that supports PnP and creates a number of (unnamed) FDOs - one per device supported by the driver. This driver then creates a single, named, legacy management Device Object using IoCreateDeviceSecure that’s used for overall administration of its devices. The driver might want tight security applied to the management Device Object, but relatively open security on its normal (FDO) device units.
So, what GUID do we pass into IoCreateDeviceSecure then? Your first inclination might be to pass in the device’s actual setup class GUID. But that is probably a bad idea. Why? Because if the driver calls IoCreateDeviceSecure and specifies the device setup class associated with its FDO devices, one of two things will happen.
a) The management Device Object will be created with security that’s too open or,
b) the device units will have a security descriptor applied that is too tight!
How could this happen? The legacy management Device Object will be created with security that’s too open if the device’s INF file specified an open level of protection for its devices. Because security was specified in the INF, and the DeviceClassGuid used in IoCreateDeviceSecure matches the device setup class GUID in the INF, this protection specified in the INF took precedence over the security specified when IoCreateDeviceSecure was called.
On the other hand, the Device Objects for the device units will have a security descriptor that’s too tight if the device’s INF file did not specify a security descriptor. As a result, the security descriptor provided by IoCreateDeviceSecure was used to establish the default security for Device Objects of that device setup class, again specifically because DeviceClassGuid matches the device setup class GUID in the INF. Result? The same protection that was applied to the management Device Object will be applied to the device units.
Confusing? Well, yeah. But thankfully the solution to this problem is easy. When you need to create a Device Object with unique security attributes, such as the legacy management device object in our example, you simply specify a unique GUID for the DeviceClassGuid parameter. That avoids conflict with the DeviceClassGuid that’s used for the device units. Problem solved!
How not to Use It
By now you might be thinking, “This IoCreateDeviceSecure thing is pretty cool. I think I’ll replace all my calls to IoCreateDevice with it.” Not so fast. IoCreateDevice Secure works for named Device Objects only. It returns an error if you try to call it with a NULL device name and you haven’t also specified the device characteristics value FILE_DEVICE_AUTOGENERATED_NAME. This is because a typical unnamed FDO that is attached to an underlying PDO stack is accessed via its underlying named PDO.
Another place where you won’t want to use IoCreateDeviceSecure is when you create a named FDO and attach it to the underlying PDO in your PnP stack. The problem here is that naming the FDO in this way creates an entirely separate entry point to that device stack. This isn’t an issue if you specified security in your INF, as the system will propagate that security specification to all the device objects in the stack uniformly. On the other hand, failing to do this can leave the FDO and PDO with different security settings — And, of course, an attacker could just open the less-protected one.
Remember, in the end the only proper way to secure a PnP stack is by specifying security in your INF file.
So, there you have it. A whole new DDI for you to use to lock down those named Device Objects. Remember, it’s up to you to practice safe driver writing.
OSR thanks Adrian Oney (Windows base team) for his highly entertaining assistance in preparing this article.
Keeping Secrets - Windows Security (Part III)
Keeping Secrets - Windows NT Security (Part II)
Keeping Secrets - Windows NT Security (Part I)
You've Gotta Use Protection -- Inside Driver & Device Security
Securing Device Interfaces - A Better Approach than Sending an SD
Security During Create Operations
Locking Down Drivers - A Survey of Techniques
What is Coming with Vista - Limited User Access
Rate this article and give us feedback. Do you find anything missing? Share your opinion with the community!
Post Your Comment
"DeviceClassGuid: example, values, NULL"
This was a very good article over IoCreateDeviceSecure. However, there were some things I would have liked to see to clear a few corner-case questions up.
First, an actual coding example showing a DeviceClassGuid being set up then used by IoCreateDeviceSecure() in loading a driver would have been very helpful. This should have gone in the "How to use it" section.
Next, "NT V4 style drivers can just spin a new GUID and use that." does not help me picture how I could use IoCreateDeviceSecure(). What does it mean 'can just spin a new Guid and use that'? And 'Nt V4 style drivers'...is it talking about drivers with a .sys extension? The article talks a lot about .inf driver files, but there are a good deal of .sys driver files too. The article is not clear on how to deal with drivers with the .sys extenstion using IoCreateDeviceSecure().
Finally, what happens if DeviceClassGuid parameter is NULL? What are the ramifications? I know it can be done. The system does not barf if it is done. But I do not know if there is any consequence if this is done. THere are some driver designs out there that need a name and cannot use the 'Guid' naming convention; thus, this parameter does not make sense to use if one were to try and strengthen security of these types of drivers using IoCreateDeviceSecure, does it?
11-Aug-06, James Freyensee