Recently, I have posted about various features of the kernel object namespace. Here’s the table of contents for this series:
- The kernel object namespace and Win32, part 1.
- The kernel object namespace and Win32, part 2.
- The kernel object namespace and Win32, part 3.
This posting (the last in the series) attempts to focus on the remaining two parts of the kernel namespace that are visible to Win32. These two parts are broken up as follows:
- DOS devices. DOS device names are object names that can be manipulated with the file management functions, such as CreateFile or DefineDosDevice. The set of DOS devices also includes traditional DOS device names (hence the name), such as NUL, CON, AUX, and soforth. This object namespace is “sessionized” by Terminal Server.
- Window station and desktop objects. These are infact named kernel objects, although this fact is only barely made visible to Win32. Like DOS devices, window stations and desktops have a namespace that is “sessionized” in the presence of Terminal Server.
Frist, I’ll cover how DOS devices are “sessionized”. This information is as of how the namespace is implemented in Windows XP and later.
In Windows XP, DOS devices are virtualized based off of the LSA logon session ID a token has associated with it (otherwise known as an authentication ID). This value is actually a bit more granular than the Terminal Server session ID.
While a Terminal Server session ID represents a complete “terminal” (essentially, a complete input/output stack with all of the software support that goes with that – a “session”), an LSA logon session ID simply represents an instance of a user that is logged on to the local computer.
Now, in normal circumstances, there is typically a very similar correlation between Terminal Server sessions and LSA logon sessions. For each Terminal Server session, there is typically at least one LSA logon session, which corresponds to the user that is logged on interactively to that Terminal Server session. However, this is not necessarily a one-to-one relationship; for instance, a Terminal Server session that is idle does not really have an LSA logon session ID associated with it (other than perhaps the system logon session, under which winlogon and csrss operate). Or, a Terminal Server session could have many LSA logon session IDs associated with it; think of the case where a user starts a program under a different account with the “RunAs” command. To add to that, some LSA logon session IDs may not even correspond to a local user at all; for example, a remote network logon from a different computer that corresponds to an attempt to access a share hosted on the local machine may be given a logon session ID, even though it corresponds to no user physically logged on to the local machine.
The reason why DOS devices must be virtualized is fairly obvious; different users may have different drive maps, and you need to be able to map the same drive letter as someone else in a different session. The way this was handled needed to be more granular than Terminal Server sessions, though (at least in Windows XP); this is due to how RunAs interacts with drive share access and network drive mappings.
Specifically, the behavior of associating DOS devices with an LSA logon session ID is used because the credentials used to access a remote share are associated with the LSA logon session of the calling user. This means that if you start a program under RunAs, it won’t have access to the same credentials as the parent LSA logon session. As a result, a program started via RunAs doesn’t really have access to network drive mappings (in particular, drive letter based network drive mappings). Because of this, it makes more sense to isolate DOS devices based on LSA logon sessions than Terminal Server sessions, as a separate LSA logon session cannot access drive share connections made by a different LSA logon session anyway.
The resulting architecture that isolates DOS devices on a per-LSA-logon-session basis thus operates as follows:
For each Terminal Server session ID, there is an object directory of the name \Sessions\<TS-session-ID>\DosDevices. Although this object directory is created under the session-named directory for each Terminal Server logon session, only the object directory \Sessions\0\DosDevices is actually used in Windows XP and beyond.
In the the \Sessions\0\DosDevices directory, there is a set of subdirectories that are each named by a LSA logon session id, in the form of a 64-bit hexadecimal number with two 32-bit halves separated by a dash, with each 32-bit half padded out to 8 characters by leading zeros (Terminal Server session IDs used to name the \Sessions\<TS-session-ID> object directories are decimal with no leading zeroes). So, you might see a directory named in the form \Sessions\0\DosDevices\00000000-00b73dfe for an LSA logon session ID 0x0000000000b73dfe.
Each of these object directories named by an LSA logon session ID contains any DOS devices that were created by that LSA logon session. The way the system evaluates DOS device names is by first trying the LSA logon session local object directory, and then trying the global DOS device object directory (which is named \GLOBAL??, and has DOS devices that are associated with no one session in particular, such as physical hard drive DOS device names like C:, or predefined DOS devices like NUL or PhysicalDrive0). To override this lookup behavior and specify that a DOS device name only references the corresponding object in the global DOS device directory, the “Global\” prefix may be used with the DOS device name (similar to named kernel objects, such as mutex objects). This support is handled by a symbolic link named “Global” that is created in each LSA-logon-session ID-named object directory. This symbolic link refers to the “\GLOBAL??” object directory. (Of course, only \DosDevices is publicly documented, to my knowledge.)
If you’ve been reading the previous articles, you may be wondering how the “\DosDevices” and “\??” object directories relate to the “\GLOBAL??” object directory that I just said contained all global DOS device symbolic links (after all, the DDK documents these symbolic links as being present in “\DosDevices”). Well, the way it ends up working out is that there is a symbolic link named “\DosDevices” which points to “\??”. The “\??” name is a bit of a hack; it is not actually a real object name, but instead a hardcoded special case in the object manager’s parser routines that is interpreted as a reference to the global DOS device directory. (This was done for performance reasons; it is very common to look up objects in the \DosDevices directory, and adding a special case check first, before the normal object directory traversal speeds up name translations for Win32 names a bit.) As a result, you can actually use “\??, “\DosDevices”, and “\GLOBAL??” to refer to global DOS devices; they all end up pointing to the same underlying location (eventually).
The other part of Terminal Server session instancing on the kernel object namespace deals with window stations and desktops. Window stations for a particular Terminal Server session are placed in a directory named in the form of \Sessions\<TS-Session-ID>\Windows\WindowStations\<Window-Station-Name> (desktop objects have names that treat a window station as an object directory, such as \Sessions\2\Windows\WindowStations\WinSta0\Default). Note that for session zero, this convention is slightly different; instead, window stations for session zero reside in \Windows\WindowStations.
That wraps up the series on the kernel object namespace (as it relates to Win32). By now, you should have a working understanding of how to use the kernel object namespace from Win32, and how the “magic” behind Terminal Server and LSA logon session isolation is actually implemented. (It is worth noting that the abstraction layer that Win32 provides added real value here, as there were no gross hacks required to be added on to the kernel object namespace to support Terminal Server. Instead, everything is implemented as plain object directories and symbolic links.)
Terminal Server session isolation for the rest of Win32 (namely, the input/output portion) is much more complicated and is hardly as clean as the separation imposed on the object namespace is. That, however, is a story for a different series…
Wonderful series, thanks!
I’ve been trying to understand some of the new stuff in 2003 and it still confuses me (In the Object Manager Parsing Callback). LUID Object Directories, “Shadow” DOS Device Table…
How can we enumerate winstations and desktops from a specific terminal session? I presume NTQueryDirectoryObject? Can you help…
Assumming that you are speaking of “winstation” == “window station” and not “terminal server session” (which are internally called “winstations”, not to be confused with “window stations”), the supported way to do that is to start a process in the target session that uses EnumWindowStations / EnumDesktops. You could try to get something working with traversing the object directory, permissions allowing, but this is of course undocumented and subject to change without notice.
But it might first be better to examine why you want to do that in the first place. There might be a better means to what you’re really trying to do…
You can look at the hierarchy with an “Object View” utility, there are many such available as Freeware on the Worldwide Web. The Namespace will have many entries, organized as a tree rooted at “\”. One of the directories in that hierarchy is \device, which contains every device visible by the system. When DriverEntry creates a DeviceObject named “cdriver”, for example, an entry for that object will be created at \device\cdriver . However, this is not the name that is seen by the application: what the app really sees is a link to the \device\cdriver name. For example, the name “C:” is really a link into the \device entry for drive C.
[…] to this behavior in this KBase article, and you can learn more about the mechanics of the feature in this blog posting). The gist of the feature is that mapped drives created by one logon session are not accessible to […]