Path prefixes \??\ and \\?\

In NT, “\??\” is a path prefix that stands for the object directory that’s reserved for a user’s devices, or more specifically, device aliases. A device alias is implemented in the object namespace as a symbolic link that typically resolves to a device object in the “\Device” directory. Sometimes in the documentation these device aliases are referred to as “junctions” in the object namespace, which is not to be confused with “directory junctions” (or mount points) in a file system.

Using the “\??\” prefix instructs the object manager to search in the caller’s local device directory, “\Sessions\0\DosDevices\[Logon Authentication ID]”, which is coupled to (i.e. shadows) the global device directory, “\Global??”. For efficiency, both of these directories are cached by an access token’s associated logon-session record and also by each process object. The SYSTEM logon (ID 0x3E7) uses “\Global??” as its local device directory. Note that the local directory has a “Global” link to allow accessing global devices when a local device shadows the global one (e.g. “\\?\Global\Z:”), or to allow a device driver to create a global device when not executing in a SYSTEM thread. NT originally used a single “\DosDevices” directory, regardless of the caller. With the introduction of Terminal Services and fast-user switching in NT 5, they had to generalize it to the present system of local and global devices. Nowadays, for backwards compatibility, “\DosDevices” is a link to “\??”.

Translating DOS paths to native NT paths is implemented by NT’s user-mode runtime library (i.e. the Rtl prefixed functions that are exported by “ntdll.dll”).

The straight-forward case is a path that’s prefixed by either “\\.\” or “\\?\”. This is a local device path, not a UNC path. (Strictly speaking it’s in the form of a UNC path, but “.” and “?” are reserved device domains.) For this case, the prefix is simply replaced by NT “\??\”. The difference between the two WINAPI device-path prefixes is that a “\\?\” path (all backslashes, no forward slashes) is a so-called “extended” path, which bypasses all normalization, whereas a “\\.\” path gets normalized.

Device path normalization resolves “.” and “..” components, replaces forward slashes with backslashes, and strips trailing spaces and dots from the final path component. Because forward slashes are translated to backslashes, the prefix of a normalized device path can be “//./” or “//?/” or any combination of slashes and backslashes, except for exactly “\\?\”. Note that if the process doesn’t support long paths, normalized paths are limited to less than MAX_PATH (260) characters. (Long-path support can be enabled in Windows 10 through a combination of registry and application manifest settings; consult the relevant documentation.) GetFullPathNameW handles both prefixes equivalently and even normalizes an extended path that begins with “\\?\”.

UNC paths are also unsurprising. The runtime library simply replaces the leading “\\” in the normalized path with an explicit reference to the “UNC” device, i.e. “\??\UNC\” (e.g. “\\server\share” -> “\??\UNC\server\share”). Note that “\Global??\UNC” is a symbolic link to “\Device\Mup”, the Multiple UNC Provider device, which is responsible for mapping the “server\share” to the correct UNC provider (e.g. to the LanmanWorkstation redirector for an SMB share).

DOS drive paths (i.e. those beginning with an “[A-Z]:” drive) are interesting in a couple of cases. The first is that the runtime library supports per-drive working directories using conventionally ‘hidden’ environment variables such as “=C:”. For example, “C:System32” resolves to “C:\Windows\System32” if the “=C:” environment variable is set to “C:\Windows”. Also, if the last component of the path is a reserved DOS device name, including if the name has trailing colons, spaces, dots, and even a file extension, the path gets translated to a device path (e.g. “C:\Windows\nul: .txt” -> “\??\nul”). (DOS devices are also reserved in the final component of relative paths that have no drive.) Otherwise, the runtime library simply prepends “\??\” to the normalized path (e.g. “C:/Windows” -> “\??\C:\Windows”).

A DOS drive such as “C:” (i.e. “\Global??\C:”) is an alias for an NT volume device (i.e. an object symbolic link). The NT device name is not persistent and is typically enumerated, so the final target depends on the relative order in which volumes are added, and it may even change if a volume is removed and subsequently restored. For example, the final NT path for “E:\Temp” on a removable drive may start out as “\Device\HarddiskVolume8\Temp” and then, after removing and reinserting it, the new final path is “\Device\HarddiskVolume10\Temp”. The mount-point manager implements persistence using the unique ID of a volume, which it associates with a volume GUID name (e.g. “Volume{00000000-0000-0000-0000-000000000000}”) and optionally (usually) a DOS drive letter. The GUID name is used to implement volume mount points in file systems that support junctions (i.e. IO_REPARSE_TAG_MOUNT_POINT reparse points), such as NTFS and ReFS.

Leave a Comment