Hello @Chris Terrell ,
Thanks for splitting this out. On the setup I tested, a RootDirectory handle whose granted mask is FILE_READ_ATTRIBUTES alone needs no extra right for that child request. Two of your three items are documented; the third is only empirical.
Child access check: documented, and it runs against the child's own security descriptor, not the parent's. File Security and Access Rights: "the security descriptor of a parent directory is not used to control access to any child file or directory." So your child DesiredAccess (FILE_READ_ATTRIBUTES) is checked against the child, independent of the parent handle.
Traversal check: documented, and it runs against the caller's token and privileges, not the parent handle's mask. FILE_TRAVERSE is only enforced "by removing the BYPASS_TRAVERSE_CHECKING privilege from users" (same page, and File Access Rights Constants says it is "ignored" by default). That privilege is SeChangeNotifyPrivilege, granted to Everyone by default, so the traverse check is normally skipped.
Parent handle granted mask: this is the part the contract does not specify. Neither ntifs NtCreateFile, NtOpenFile, nor OBJECT_ATTRIBUTES ties any bit to the RootDirectory handle's granted mask for a relative open. So the docs are silent, and I would not read that silence as either "a bit is required" or "no bit is required." The mechanism is ObReferenceObjectByHandle: the requested access is compared against the handle's granted access. What the docs leave open is which access, if any, the I/O manager requests for that reference.
That gap is what I measured:
- Environment: Windows 11 build 26200, local NTFS, caller is the owner, default privileges.
- Child shape: exactly yours (one component,
Attributes = 0,DesiredAccess = FILE_READ_ATTRIBUTES,ShareAccess = FILE_SHARE_READ,CreateDisposition = FILE_OPEN,CreateOptions = FILE_OPEN_REPARSE_POINT). - Parent open and granted mask: opened with
NtOpenFile(not Win32CreateFile, which silently addsSYNCHRONIZEandFILE_READ_ATTRIBUTES), requesting one access value at a time and noSYNCHRONIZE, then read the granted mask back independently withNtQueryObject(ObjectBasicInformation).GrantedAccessand confirmed it equaled what I requested. - Results: with the parent granted mask measured as
FILE_READ_ATTRIBUTES(0x80) alone, the child open returnedSTATUS_SUCCESS. Same result withFILE_TRAVERSE(0x20) alone,FILE_LIST_DIRECTORY(0x01) alone, and evenREAD_CONTROL(0x20000) alone with no file-specific bit.
The READ_CONTROL-only case is the telling one: a parent handle with no directory-specific bit still worked, so the reference did not demand FILE_TRAVERSE or FILE_LIST_DIRECTORY on this build. For your exact question, FILE_READ_ATTRIBUTES alone was enough; the handle only had to be valid and still open.
One note on the earlier OBJ_DONT_REPARSE thread: there I opened the parent with Win32 CreateFile requesting FILE_LIST_DIRECTORY | SYNCHRONIZE and did not read back its granted mask, so only the requested access is known for that test. The independent measurement above was done specifically for this question.
I hope this clarifies the parent-handle requirement for your scenario. If anything needs more detail, feel free to follow up and I'll be glad to help. If you found my response helpful or informative, I would greatly appreciate it if you could follow this guide for your confirmation
Thank you.