Recent Posts

Tags

News

  • A blog about Microsoft Windows development, focused on kernel-mode driver development, the Windows DDK, WDK, and related tools.

    To elaborate on the copyright notice at the bottom: all content produced by me on this site is copyright and licensed as follows:

    <!-- Creative Commons License --> Creative Commons License
    This work is licensed under a Creative Commons License. <!-- /Creative Commons License --> <!-- <rdf:RDF xmlns="http://web.resource.org/cc/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <Work rdf:about=""> <dc:type rdf:resource="http://purl.org/dc/dcmitype/Text" /> <license rdf:resource="http://creativecommons.org/licenses/by-nc/2.0/" /> </Work> <License rdf:about="http://creativecommons.org/licenses/by-nc/2.0/"> <permits rdf:resource="http://web.resource.org/cc/Reproduction" /> <permits rdf:resource="http://web.resource.org/cc/Distribution" /> <requires rdf:resource="http://web.resource.org/cc/Notice" /> <requires rdf:resource="http://web.resource.org/cc/Attribution" /> <prohibits rdf:resource="http://web.resource.org/cc/CommercialUse" /> <permits rdf:resource="http://web.resource.org/cc/DerivativeWorks" /> </License> </rdf:RDF> -->

    Although I work for Positive Networks, this work is my own and is not connected with my employer in any way.

    <!-- technorati again --> <script type="text/javascript" src="http://embed.technorati.com/embed/8xz8dihr.js"> </script>

Community

Email Notifications

Other Blogs

General

Technical Resources

About Me

Archives

Kernel Mustard

Reflections on Windows System Programming
Steve Dispensa, MVP - Windows DDK

Some IOCTL Code Definition Tips

We've all* defined our own IOCTL codes in the past. They're a primary way to enable user-mode -> driver and driver -> driver communication. Although they were probably originally envsioned in the context of storage-related drivers, Microsoft supports the use of an IOCTL dispatch in most kinds of drivers. NDIS, for example, supports NdisMRegisterDevice for the explicit purpose of providing access to the IOCTL framework in network drivers.

IOCTL codes are defined using the CTL_CODE() macro, which is part of ntddk.h in kernel-mode and winioctl.h in user-mode. The first parameter takes a Device Type, which can either be one of the Microsoft-defined device types (see the DDK headers for a list) or a custom device type code.

There are a couple of things to keep in mind here. The first is that the Device Type parameter must match the device type that is passed into IoCreateDevice(). Also, If you define a custom device type, it should be above 0x8000. The bottom 15 bits are what actually represent the device type, and the 16th bit is known as the Common bit. The DDK requires that the Common bit be set on all custom Device Types. Another way of saying this is by requiring all custom codes to be between 0x8000 and 0xFFFF. Similarly, the function code is required to be between 0x800 and 0xFFF, because the top bit ("Custom" in this case) is required to be set for non-Microsoft-defined function codes. Playing by the rules will make your driver as compatible as possible with all releases of the OS, present and future.

Method is one of METHOD_BUFFERED, METHOD_IN_DIRECT, METHOD_OUT_DIRECT, or METHOD_NEITHER. METHOD_BUFFERED is the most common transfer method, and is generally the safest and easiest to use. This method double-buffers your data by copying it from the supplied user-mode buffer into a newly-created kernel-mode buffer, and then passing that new buffer to your driver instead of the original one. If you're transferring less than one page of data (4K on x86), and especially if you'r doing it infrequently, this is the way to go. If you're tranferring larger amounts of data, one of the DIRECT methods may make sense. This is particularly true if you're going to wind up DMAing your data to or from a device, but it is also true if you just want to avoid the double-buffer in general. I'm not going to discuss METHOD_NEITHER at the moment, other than to say that you shouldn't use it. I'll get in to more detail about why another day.

The final knob to turn is the RequiredAccess parameter. I admit that I really didn't understand what this parameter was for until quite a while after I wrote my first driver. It turns out that it is a method for enforcing some small but nontrivial amount of access control on who can call your IOCTL. This specifies the kind of access the user must have to the device, as specified in the CreateFile() call, in order for the IO manager to let the IRP through. FILE_ANY_ACCESS means that they can send the IRP with virtually any access at all, as long as they have an open file handle to the driver. FILE_READ_ACCESS and FILE_WRITE_ACCESS loosely correlate to the ability to read and write data to and from the device.

Most driver writers just set this to FILE_ANY_ACCESS and forget about it. This is, of course, exactly the wrong thing to do. A much better strategy is to specify the most restrictive access possible (FILE_READ_ACCESS|FILE_WRITE_ACCESS -- yes, you can OR them together) whenever possible, and only remove bits when necessary ("necessary" depends on the kind of driver you're writing). This parameter is particularly important in IOCTLs where you're actually reading and writing data -- why would you allow a user to read data from an IOCTL if you wouldn't allow the same user to read data using ReadFile()? -- but it should probably be applied carefully to all IOCTLs.

Finally, it might be obvious, but try to name your IOCTL codes something obvious. My office has a standard that goes IOCTL__. In other words, you might have IOCTL_POSVPN_SET_INFO to configure our VPN driver. This goes with the standard rants about variable naming, and is generally an important thing if you want someone else to be able to work on your code.

OK, I expect everyone to run out and tighten up their use of CTL_CODE(). When you're done with that, go listen to Fred Jones, Part 2, by Ben Folds. It'll make you a Better Person.

Happy hacking! * OK, "All" might be a bit of an exaggeration. :-)

Comments

Steve Dispensa said:

Note that by default, all users can open a device for FILE_READ_DATA | FILE_WRITE_DATA.

This is what the default security descriptor looks like for device objects:

Security descriptor:
--------------------
Owner: BUILTIN\Administrators
Primary group: NT AUTHORITY\SYSTEM
Revision: 1, Control: DaclPresent
Dacl: Revision 2 Size 92 bytes used, 0 bytes free, 4 ACEs present
Ace: Index 0 Flags: <empty> Type: AccessAllowedAce SecurityPrincipal: Everyone
AccessMask: ReadData WriteData AppendData ReadEA WriteEA Execute ReadAttributes WriteAttributes ReadControl Synchronize
Ace: Index 1 Flags: <empty> Type: AccessAllowedAce SecurityPrincipal: NT AUTHORITY\SYSTEM
AccessMask: ReadData WriteData AppendData ReadEA WriteEA Execute DeleteChild ReadAttributes WriteAttributes Delete ReadControl WriteDac WriteOwner Synchronize
Ace: Index 2 Flags: <empty> Type: AccessAllowedAce SecurityPrincipal: BUILTIN\Administrators
AccessMask: ReadData WriteData AppendData ReadEA WriteEA Execute DeleteChild ReadAttributes WriteAttributes Delete ReadControl WriteDac WriteOwner Synchronize
Ace: Index 3 Flags: <empty> Type: AccessAllowedAce SecurityPrincipal: NT AUTHORITY\RESTRICTED
AccessMask: ReadData ReadEA Execute ReadAttributes ReadControl Synchronize
Sacl: not present

You can use IoCreateDeviceSecure to specify your own security descriptor, or you can build it yourself with the appropriate Rtl routines (for the most part, the ones you need are only documented by the IFS kit though).

Another option available for restricting access is to use SeCaptureSubjectContext / SeLockSubjectContext SeTokenIsAdmin / SeUnlockSubjectContext (these also require the IFS kit) and deny access manually if the requestor isn't KernelMode or an admin (SYSTEM counts as an admin).

Finally, you could use SeSinglePrivilegeCheck to require a specific NT privilege for a request.
# September 3, 2004 12:58 AM
Leave a Comment

(required) 

(required) 

(optional)

(required)