Since the introduction of Windows 2000, Microsoft has strongly urged us to create Functional Device Objects (FDOs) that are unnamed. In fact, the WDK documentation tells us, "WDM... function drivers do not name their device objects."
I say this is nonsense.
In place of device names, the WDK advises us to use Device Interfaces. Device Interfaces allow a driver to register a device object as supporting a specific interface class, which is specified by a GUID. The interface class implies the services provided by the device and the I/O function codes (including IOCTLs) the device supports. Device Interfaces make a number of interesting things possible, including:
When Device Interfaces are used in place of device names, any potential for naming conflicts is avoided. I've never had a problem with one of my device objects having the same name as another device on the system, so I consider this advantage dubious. Also, folks who support the use of Device Interfaces note that because device interface classes are specified as GUIDs, this removes any dependency on English-language device names, and thus (at least potentially) makes device usage more international friendly. Of course, the manifest constants that are associated with those GUIDs are typically in English (such as GUID_DEVINTERFACE_ COMPORT) so this isn't really much of an advantage.
Overall, however, Device Interfaces are clearly a good thing, and are a useful feature that should be used by any driver. In fact, we use Device Interfaces in (almost) every driver we create here at OSR.
So, what's not to like?
What's not to like about Device Interfaces? Well, nothing, really. The problem isn't with Device Interfaces per se, but rather with the idea of using Device Interfaces in place of (not in addition to) FDO device names. Not naming your (FDO) device does nothing other than penalize your users for your choice. When you name your FDO, users can open your device with one line of code specifying the device name. Everyone on the planet is familiar with this method:
hFile = CreateFile("\\\\.\\MyDevice", GENERIC_READ|GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0);
When you use a Device Interface but no device name, users have no choice but to open your device by using the Device Interface GUID. This is "somewhat more difficult" than the one line of code you see above. In fact, Figure 1 contains a shortened extract from a WDK sample of the code necessary just to retrieve a device symbolic link given a Device Interface GUID. It has most of the error handling and message display code removed, and it's almost 100 lines long. Oh, and after you call this function you still need to call CreateFile.
Figure 1 -- Is This Easier Than 1 Line of Code?
In the world in which I live, devs aren't going to learn, write, debug, and maintain 100 lines of code when one line will do.
So, why does Microsoft encourage us to not name FDOs? It all has to do with some mistakes made back during the development of Windows 2000.
Bus Drivers, PDOs and FDOs
Let's review: You know that when a bus driver discovers a device instance in the system, it creates a device object that represents the physical instance of that device on the bus. This device object is the Physical Device Object, or PDO. The bus driver reports the existence of this PDO to the PnP Manager. After a few queries between itself and the bus driver, the PnP Manager determines which driver is capable of controlling the functionality of the device, and subsequently loads that function driver. When loaded, the function driver creates another device object that represents the functional aspect of the device called the Functional Device Object or FDO. The function driver attaches the FDO it creates to the underlying PDO, thus creating a "device stack" (which is also sometimes referred to as a DevNode).
The relationship between the FDO and PDO is shown in Figure 2. Note that if there are any filter drivers present in the device stack, their device objects will be located either above or below the FDO (and never below the PDO). It's important to keep in mind that whenever any device in the device stack is opened (the PDO, the FDO, or any filter device objects), subsequent I/O requests always go to the top of the device stack.
Figure 2 -- An FDO over a PDO
Problems arise due to some curious architectural choices that were made while first implementing PnP in Windows 2000. For example, PDOs -- the device object created by the bus driver to represent the physical aspect of the device on the bus -- are always named. However, because the bus driver often doesn't intend this name to ever be used to access the device, the name can be arbitrary and meaningless (such as"NTPNP_PCI0005").
Another curious Win2K architectural choice is that any Device Interface, if support for one is registered by the function driver, points to the PDO and not the FDO.
Because the PDO (created by the bus driver) is always named, the device stack can always be accessed (from a user-mode application or a kernel-mode module) by opening the PDO. Because the Device Interface points to the PDO, accessing a device by its Device Interface is equivalent to accessing it by opening the PDO.
Whenever a user attempts to open a given device, the I/O Manager checks the user's access rights against any access control that exists on the specific device being opened. Thus, when a user opens a PDO (by name or by Device Interface) the I/O Manager only checks to ensure that the user has access to the PDO. Any access controls that may exist on any other device objects in the device stack are not checked. This means that WDM device stack characteristics and access control are primarily in the hands of the bus driver, because it's the bus driver that creates the PDO. And it's the PDO that's intended to be the access point into the device stack.
What makes this much less than optimal is the fact that the function driver is the entity that's primarily responsible for implementing support for I/O requests in the device stack. As a result, the function driver is really in the best position to determine what characteristics and protections should be applied to the interfaces into that stack. However, in WDM that responsibility is given to the bus driver.
So, WHY not name FDOs?
Microsoft tells us not to name FDOs because doing so creates a second access point in the device stack. If an FDO is not named, it cannot be directly opened. When a named FDO is created, a user can open the FDO using that name instead of only being able to access the device stack via the PDO.
When a user opens a named FDO, access checks are performed on the FDO (as the target of the open request) and any protection that the bus driver applied to the PDO isn't considered. This is Microsoft's primary concern: Naming FDOs creates two access points in the device stack and any access controls that the bus driver may have specified can be bypassed.
By default, however, bus drivers tend to apply minimal access controls to the device objects they create. And, if you think about it, this is as it should be. Bus drivers don't typically understand how devices are expected to be used, what users should have access to them, or how the devices should be protected. These sorts of things are the purview of function drivers.
This, finally, leads us to the real security issue that should concern us all: Function driver writers, whether they name their FDOs or not, must specify any access protections that they require in the device stack. If you fail to specify any access protection for your device stack, you get whatever access protection the bus driver decides is right for your device stack. And, given that the bus driver doesn't have any knowledge whatsoever about which users in the system should have access to your device, that protection will be useless at best and inappropriate at worst.
The device stack shown in Figure 3 illustrates the problem. Let's say we have a PCI-based missile launcher. The PCI bus driver creates a PDO for this device that allows anyone on the system to open it and send it read and write requests. This probably isn't the best protection for a missile launcher device. When we create our FDO, if we don't specify any access controls, we get whatever the default is that's provided for us. However, being responsible, when we create the FDO and we specify access controls that only allow the system (specified as "SY" in SDDL) and built-in Administrator account (specified as "BA" in SDDL) to have access, and give no access to other users on the system. The good news? If we set this protection properly (see Of SDs, ACLs and INFs), the I/O Manager will automatically propagate it throughout the device stack.
Figure 3 -- Access Points in the Device Stack
Naming FDOs Not a Problem
Once you understand the overall architecture, you realize that there's no reason not to name your FDOs. And naming your FDOs most certainly does not bypass "the PnP manager's security features", whatever those might be, as stated in the WDK docs.
It is certainly true that naming your FDOs creates an alternative access point into the device stack that would not be there if your FDO was not named. But, given that any access controls specified for your device by the bus driver are likely to be arbitrary, you're going to need to specify the access controls for your device stack in any case. And if you're going to do this, you can easily insure they're applied to both the FDO and PDO in your device stack.
About Device Interfaces and Device Names
I hope you see now that the issue isn't whether to use Device Interfaces or name your FDOs. In fact, when we do projects here at OSR we usually do both: We name our FDOs, and we register support for a Device Interface. This allows the user to access the device easily, in the conventional way, by name. It also allows the user to enumerate devices by Device Interface if they wish, or to register for Device Interface change notifications.
The real issue is that when you write a function driver, unless you don't care which users have access to your device stack, you must specify access protection. This is true whether you name your FDO or not! Failing to specify access protection via your function driver will almost certainly result in your device stack having unsuitable access controls, or no access controls at all.
Again, you can read all about how to specify device object protection in the article Of SDs, ACLs and INFs.