Ever wonder how the creation of a handle works? It doesn't matter type of resource the handle you are opening is backed by (a COM port, a file, a network share, a custom piece of hardware, etc), it all goes through CreateFile (which should be a little obvious since the only way to open an type of handle is by calling it (with the exceptions like this function or this function)). The IO manager creates a file handle for any resource type the same way. In theory, the way the IO manager handles file creation should be transparent to most drivers, but in reality a driver writer should understand how it works so that you can configure your device properly and implement IRP_MJ_CREATE properly.
The key concept to understand here is that the IO manager does not have hard coded knowledge of each of these resources, it is completely generic. This means that the IO manager does some parsing/path evaluation and then relies on the driver to perform some additional optional path evaluation. Very broadly, here is what the IO manager does
- It tries to find the device name in the lpFileName parameter. More than likely the path will contain a symbolic link to the device name instead of the device name itself. This means that the IO manager will evaluate the symbolic link to find the target string and then restart the parse.
- Once the device name is found, it used to find the device object (i.e. a PDEVICE_OBJECT).
- If there is no remaining path name after the device name/symbolic link, use the device object's security descriptor to evaluate if the caller has sufficient rights to open the device object. If there is a remaining path, no security check occurs (*)!
- Create a file object, set the FileName to the remaining path and send a IRP_MJ_CREATE IRP to the device
That's it. The FileName is the important part here. This is how the driver provides additional path evaluation. It is up to the driver to evaluate the FileName properly and apply its own security checks based on the string. In WDM terms, the FileName value is a part of the device's namespace. Each device defines its own namespace, the WDK topic "Controlling Device Namespace Access" explains device namespaces and does a decent job, but without the context of how the IO manager works, it is a bit indecipherable.
Let's look at a few sample file names and see how they would parse and the consequences of each example
|lpFileName||Device name||FileName||Security access check in IO manager|
The "COM1" case is straightforward. The COM port is being opened, the IO manager evaluates whether the caller has access and it is done, but why "COM1\Foo" ? I want to illustrate that you can append a path name to any symbolic link that you want and that it might result in failure, different behavior or be completely ignored (it will nearly always be ignored). When opening "COM1\Foo", the IO manager will pass "\Foo" to the serial driver and it will be subsequently ignored. The path name can have meaning though as I wrote about earlier if you want to distinguish which device interface is being opened.
"C:\Windows\win.ini" is what many consider to the normal usage for CreateFile, a file is being opened. "C:" is evaluated to particular volume and then the create is sent to the file system mounted on this volume. The file system then parses "\Windows\win.ini" to determine if the caller has sufficient rights to open the file. But what about the last example, "C:"? This path name (without the root directory "\") is asking for raw access to the volume itself (allowing you to read and write sectors directly).
I used these last 2 examples to show why the IO manager would skip the security check (step #3) if there is any remaining path. Access to "C:" itself requires administrative privileges while access to a file on C: may not have any security at all. Both the volume and file system must participate in evaluating whether the current caller has sufficient rights to open a particular file handle depending on its path. But this also brings up a very nasty security hole. I can append any path to a non file system device name, cause the IO manager to skip the security check, and if the driver does not do its own security check (which it probably doesn't since it is relying on the IO manager to do it), gain access to the device regardless of the device's security descriptor. This is a very valid concern and I will cover how to handle this in my next post.
(*) Actually, it is not always skipped, but that is the next topic ;).