Hello @Chris Terrell ,
I tested this on Windows 11 (build 26200) and OBJ_DONT_REPARSE is honored by user-mode NtCreateFile even when you pass a RootDirectory handle plus a single relative ObjectName. It behaves exactly the same as when you pass a fully-qualified path.
My reading is that the two doc pages aren't really contradicting each other; one of them is just out of date. The real definition of the flag lives on the OBJECT_ATTRIBUTES page, and it says:
OBJ_DONT_REPARSE: If this flag is set, no reparse points will be followed when parsing the name of the associated object. If any reparses are encountered the attempt will fail and return aSTATUS_REPARSE_POINT_ENCOUNTEREDresult. This can be used to determine if there are any reparse points in the object's path, in security scenarios.
Attributes is a generic object-manager bitmask that gets processed while the name is being parsed, so it applies to the Nt*File routines in general, not just to whatever a single page happens to list. The line on the NtCreateFile page saying Attributes "can be zero or OBJ_CASE_INSENSITIVE" reads to me as old, simplified wording that predates this flag. If you'd like it corrected, the quickest way is to use the "Was this page helpful?" thumbs-up/thumbs-down control in the right-hand column (just under the "In this article" outline) and note that Attributes also accepts the object-attribute flags from OBJECT_ATTRIBUTES (such as OBJ_DONT_REPARSE); that feedback goes to the docs team who own the page.
Below is what I actually saw. I made a directory junction link pointing at real with mklink /J, opened the parent directory with FILE_FLAG_BACKUP_SEMANTICS to use as RootDirectory, then called ntdll!NtCreateFile a few different ways:
- Opening the junction by relative name (
RootDirectory+link) withOBJ_DONT_REPARSEfailed withSTATUS_REPARSE_POINT_ENCOUNTERED(0xC000050B). Without the flag it succeeded and followed the junction. - Opening the exact same junction by its fully-qualified
\??\...\linkpath with the flag gave the identicalSTATUS_REPARSE_POINT_ENCOUNTERED, so relative vs fully-qualified made no difference. - Opening a plain directory that isn't a reparse point, with the flag set, succeeded, so the flag only bites when there's actually a reparse point to cross.
- I also put the reparse point in the middle of the path (
link\inside.txt) and it still failed with the flag and succeeded without it, which lines up with "any reparse in the path," not just the final component.
For contrast, FILE_OPEN_REPARSE_POINT did the opposite of OBJ_DONT_REPARSE: it opened the reparse point itself and returned success. So the two are for different goals, one opens the link, the other refuses if a link is anywhere in the way.
One thing worth calling out is that STATUS_REPARSE_POINT_ENCOUNTERED (0xC000050B) is not the same as STATUS_REPARSE (0x00000104). The "NtCreateFile never returns STATUS_REPARSE" note on the doc page is talking about that other status, not about this failure.
So the bottom line, on the configuration I tested, is that OBJ_DONT_REPARSE works with a directory-relative name. You get STATUS_SUCCESS if there's no reparse point in the path, and STATUS_REPARSE_POINT_ENCOUNTERED if there is one. I didn't hit any version or file-system dependency in my testing (verified on Windows 11 build 26200); in my reading the governing text is the OBJECT_ATTRIBUTES page, and the NtCreateFile wording is just stale. See below for where the documentation stops.
Exact test configuration
Directory junction link pointing at real, made with mklink /J, on a local NTFS volume (C:). The parent directory was opened once with Win32 CreateFile requesting FILE_LIST_DIRECTORY | SYNCHRONIZE with FILE_FLAG_BACKUP_SEMANTICS, and that handle was passed as RootDirectory. I did not read back that handle's granted mask in this test, so only the requested access is known here.
Each case was one ntdll!NtCreateFile call. Shared arguments:
DesiredAccess = FILE_LIST_DIRECTORY | SYNCHRONIZE | READ_CONTROL (0x00120001)
ShareAccess = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE (0x00000007)
CreateDisposition = FILE_OPEN (0x00000001)
AllocationSize = NULL, FileAttributes = 0, EaBuffer = NULL, EaLength = 0
Constants (ntdef.h / winternl.h, Windows SDK 10.0.26100.0): OBJ_CASE_INSENSITIVE (CI) = 0x00000040, OBJ_DONT_REPARSE = 0x00001000, FILE_DIRECTORY_FILE = 0x00000001, FILE_SYNCHRONOUS_IO_NONALERT = 0x00000020, FILE_OPEN_REPARSE_POINT = 0x00200000.
Per-case parameters and recorded results:
[A] root=parent name=link attr=0x00001040 (OBJ_DONT_REPARSE|CI) opts=0x00000021 -> 0xC000050B STATUS_REPARSE_POINT_ENCOUNTERED
[B] root=parent name=link attr=0x00000040 (CI) opts=0x00000021 -> 0x00000000 STATUS_SUCCESS
[C] root=NULL name=\??\...\link attr=0x00001040 (OBJ_DONT_REPARSE|CI) opts=0x00000021 -> 0xC000050B STATUS_REPARSE_POINT_ENCOUNTERED
[D] root=parent name=real attr=0x00001040 (OBJ_DONT_REPARSE|CI) opts=0x00000021 -> 0x00000000 STATUS_SUCCESS
[E] root=parent name=link attr=0x00000040 (CI) opts=0x00200021 -> 0x00000000 STATUS_SUCCESS
[F] root=parent name=link\inside.txt attr=0x00001040 (OBJ_DONT_REPARSE|CI) opts=0x00000020 -> 0xC000050B STATUS_REPARSE_POINT_ENCOUNTERED
[G] root=parent name=link\inside.txt attr=0x00000040 (CI) opts=0x00000020 -> 0x00000000 STATUS_SUCCESS
opts 0x00000021 = FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT
opts 0x00200021 = FILE_DIRECTORY_FILE | FILE_OPEN_REPARSE_POINT | FILE_SYNCHRONOUS_IO_NONALERT
opts 0x00000020 = FILE_SYNCHRONOUS_IO_NONALERT (F and G target a file, not a directory)
A is the directory-relative case; C is its fully-qualified counterpart; E is the separate FILE_OPEN_REPARSE_POINT call with no OBJ_DONT_REPARSE; F puts the reparse point in an intermediate component.
Correction. My September 17 comment listed the case A attribute value as 0x00040040. That was a transcription error in the write-up; the value passed was 0x00001040, as shown above. On September 18 I re-ran case A both ways: 0x00001040 returns STATUS_REPARSE_POINT_ENCOUNTERED, and the literal 0x00040040 returns STATUS_INVALID_PARAMETER (0xC000000D), so the recorded result came from the correct flag.
Where the documentation stops
Keeping three things separate:
- Express documentation:
OBJECT_ATTRIBUTESdefines whatOBJ_DONT_REPARSEdoes, and itsRootDirectorydescription says relative naming is supported in general. Neither statement is qualified by relative versus fully-qualified, by theFILE_OPEN_REPARSE_POINTinteraction, or by platform. TheNtCreateFilepage's "zero or OBJ_CASE_INSENSITIVE" line is the express text there. - My interpretation: because
Attributesis a generic object-manager field processed while the name is parsed, the flag applies the same way whether the name is relative or fully-qualified, and theNtCreateFilewording is stale. That is a reading of the general wording, not a separately documented statement about the relative case. - Measured behavior: the runs above confirm that reading on Windows 11 build 26200, local NTFS.
I am not aware of a published provision, or a clarification from the documentation owners, that expressly addresses this user-mode, directory-relative use or states its platform limits. No page I can find expressly documents (a) the directory-relative case as such, (b) the OBJ_DONT_REPARSE and FILE_OPEN_REPARSE_POINT interaction, or (c) version or file-system conditions for this combination. Those parts rest on the general OBJECT_ATTRIBUTES wording, my reading of it, and the measured result, not on an express provision.
Thank you.