Repository navigation
Build Windows openat object names from NT device paths - #33874
Conversation
Dirfd-relative multi-component opens used to resolve the base directory handle to its DOS path (GetFinalPathNameByHandleW + VOLUME_NAME_DOS) and emit `\??\C:\base\rel` for NtCreateFile. The DOS translation costs a mount-manager IOCTL per open, is denied to sandboxed (AppContainer) tokens, and does not exist on volumes mounted without the mount manager. Query VOLUME_NAME_NT instead — answered from the handle itself — and emit the `\Device\...\base\rel` object name directly. The volume prefix (two components, or server\share for Mup/redirector devices, skipping session-qualified `;` components) is copied verbatim and only the remainder is normalized, so `..` clamps at the volume root exactly as the drive-letter form clamped at `C:\`. The RootDirectory discriminator in open_file_at_windows_nt_path now matches its dir/stat siblings (leading separator = absolute object name) instead of testing for a literal `\??\` prefix. Also pins fd-relative opens to the volume the handle actually lives on: the old form re-resolved the drive letter through the caller's device map on every open, so a per-session subst/net-use shadowing the letter (including via impersonation) could silently retarget relative opens. Covers the new prefix logic with unit tests, including bare-device, volume-root, Mup share, and session-qualified redirector shapes.
|
Found 4 issues this PR may fix:
🤖 Generated with Claude Code |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughChangesWindows dirfd-relative normalization now produces NT device paths while preserving device and share roots during Windows NT path handling
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
Comment |
There was a problem hiding this comment.
I didn't find any bugs, but this reworks Windows dirfd-relative path resolution and .. clamping in the core syscall layer — security-sensitive enough that a human should sign off.
Extended reasoning...
Overview
This PR changes how normalize_path_windows builds NT object names for NtCreateFile when opening a path relative to a directory handle on Windows. Instead of resolving the base handle to a DOS path (\\??\\C:\\…) via GetFinalPathNameByHandle(VOLUME_NAME_DOS), it now requests VOLUME_NAME_NT and emits \\Device\\HarddiskVolumeN\\… directly. It adds a new nt_volume_prefix_len helper to identify the volume boundary (with special-casing for Mup/LanmanRedirector UNC devices and session-qualified ;… components), copies that prefix verbatim, and normalizes only the remainder so .. clamps at the volume/share root. It also swaps the literal \\??\\ prefix check in open_file_at_windows_nt_path for the general is_absolute_windows_wtf16 predicate, and adds ~200 lines of Windows-gated unit/integration tests.
Security risks
Path normalization for dirfd-relative opens is squarely in path-traversal territory. The correctness of .. clamping now depends on nt_volume_prefix_len correctly identifying the boundary for every device-name shape Windows can return (local volumes, MUP, LanmanRedirector, session-qualified redirectors, and anything else). This code path feeds tarball extraction during bun install, so a mis-computed prefix boundary on some volume type could plausibly change where extracted files land. The buffer-bounds arithmetic and the broadened absolute-path predicate for RootDirectory selection are also worth a second pair of eyes. The reasoning in the PR description is thorough and the unit tests cover the enumerated shapes, but this is exactly the kind of change where a subtle miss has outsized consequences.
Level of scrutiny
High. src/sys/lib.rs is the cross-platform syscall wrapper layer that essentially everything file-related on Windows funnels through, and the change is a semantic redesign (different name shape, different clamping mechanism, documented behavior differences for UNC excess-.. and network error codes) rather than a mechanical refactor.
Other factors
The PR is well-researched and well-tested — the new Rust unit tests exercise the prefix-boundary shapes, .. clamping, and an end-to-end NtCreateFile open, and the description documents NtTrace verification and full fs/shell suite runs. No bugs surfaced in automated review. That said, the change is ~250 net lines in a critical Windows-only path with acknowledged behavior deltas, so it warrants a human reviewer with Windows internals context rather than bot approval.
Replace the component-counting volume-prefix scan with the subtraction technique: the handle's full NT object name minus its volume-relative name (FileNameInformation) is the device prefix, with no assumptions about device-name shapes — nested names like \Device\HarddiskDmVolumes\<x>\Volume1 and session-qualified redirector components are handled by construction, since the kernel itself places them on the device side. Mup/LanmanRedirector prefixes still extend past server\share so the `..` clamp keeps share-root parity. The volume-relative query only runs when the relative part actually contains a `..` component — without one, any split point yields the same output, so the common case (tarball extraction, plain subpaths) stays at a single handle query. When `..` is present and the two names do not compose (racing rename), the open fails with EBADF instead of guessing a boundary that could resolve `..` to the wrong file.
The bare-component routing check and the `..` detection both walked the relative part; fuse them into one component-aware pass that answers both, exiting early only once a `..` component is confirmed (which settles both answers). The drive-letter strip moves ahead of the scan so `C:..\x` is inspected post-strip; the bypass still copies the original path verbatim.
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/sys/lib.rs (1)
6709-6754: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winStrip
C:in the passthrough branch.C:foois classified as a drive-relative bare component, but this branch still copiespath, so the colon survives intoNtCreateFileand changes the object name being opened. Copyrelhere and add a regression for bare drive-relative input.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/sys/lib.rs` around lines 6709 - 6754, In the no-separator passthrough branch, copy the drive-letter-stripped `rel` slice rather than the original `path`, while preserving length checks and null termination. Add a regression test covering bare drive-relative input such as `C:foo`, verifying that `NtCreateFile` receives `foo` without the `C:` prefix.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@src/sys/lib.rs`:
- Around line 6709-6754: In the no-separator passthrough branch, copy the
drive-letter-stripped `rel` slice rather than the original `path`, while
preserving length checks and null termination. Add a regression test covering
bare drive-relative input such as `C:foo`, verifying that `NtCreateFile`
receives `foo` without the `C:` prefix.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 52ecc17e-67e5-43e4-95ef-b39e521df084
📒 Files selected for processing (1)
src/sys/lib.rs
Since the whole-base fast path, a relative part that normalizes to
nothing (".", "./") left `joined` as a bare separator, so the result
grew a trailing `\` the drive-letter form never produced. Drop the
lone-separator suffix when the prefix is the base directory's full
path; keep it when the prefix is a bare device name (volume-root
bases, excess-`..` clamps), where the separator selects the root
directory over the volume device.
|
Investigated all four before adding any auto-close lines — none meet the bar:
Leaving the description without |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/sys/lib.rs (1)
6749-6757: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winCopy
relin the bare-name fast path. Drive-relative names likeC:foostill hit this branch, so copyingpathpreserves the stripped drive prefix and passesC:foothrough toNtCreateFileinstead offoo. Switching this branch torelkeeps it aligned with the..case; update the nearby comment to match.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/sys/lib.rs` around lines 6749 - 6757, In the bare-name fast path, copy the drive-stripped `rel` slice instead of `path` so drive-relative inputs such as `C:foo` pass only `foo` to NtCreateFile. Update the nearby comment to describe copying `rel`, while preserving the existing length check, NUL termination, and WStr construction.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@src/sys/lib.rs`:
- Around line 6749-6757: In the bare-name fast path, copy the drive-stripped
`rel` slice instead of `path` so drive-relative inputs such as `C:foo` pass only
`foo` to NtCreateFile. Update the nearby comment to describe copying `rel`,
while preserving the existing length check, NUL termination, and WStr
construction.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 4cc9805a-a51b-4e35-af25-925bc5e5bddf
📒 Files selected for processing (1)
src/sys/lib.rs
Move the fused routing/dotdot scan out of normalize_path_windows_opts into bun_paths::classify_rel_t, returning per-fact booleans (any separator, any dot, whole `..` component) from a single walk. The early exit fires only once every field is final, so a bare trailing `..` still reports has_sep honestly. Covered by a platform-independent test matrix over both element widths, including the POSIX cases where a backslash is part of the component.
|
Re: both suggestions to copy the drive-stripped
|
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/sys/lib.rs (1)
6709-6736: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winDrive-relative inputs still bypass the
relnormalization here.C:foois a drive-relative path on Windows, not an ADS name, andNtCreateFilewon’t resolve it relative toRootDirectoryfor you. This fast path should copyrelhere too, or reject drive-relative inputs sodir_fdsemantics match the slow path.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/sys/lib.rs` around lines 6709 - 6736, Fix the no-separator fast path in the drive-relative handling by ensuring inputs like C:foo do not bypass normalization: copy the stripped rel slice rather than the original path, or explicitly reject drive-relative paths so dir_fd behavior matches the slow path. Update the length check, copy, and resulting WStr length consistently in this branch.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@src/sys/lib.rs`:
- Around line 6709-6736: Fix the no-separator fast path in the drive-relative
handling by ensuring inputs like C:foo do not bypass normalization: copy the
stripped rel slice rather than the original path, or explicitly reject
drive-relative paths so dir_fd behavior matches the slow path. Update the length
check, copy, and resulting WStr length consistently in this branch.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 615c10f7-f69d-4ee9-963f-a0ae84250e2f
📒 Files selected for processing (3)
src/paths/classify.rssrc/paths/lib.rssrc/sys/lib.rs
- Honor add_nt_prefix=false in the dirfd-relative branch: Win32 consumers get the \?\GLOBALROOT spelling of the same object instead of a debug_assert (release builds previously emitted \Device\ names that kernel32 silently drive-roots; reachable via the async fs.cp directory path with a relative destination). - Fail loud when a `..` component would cross the clamp floor on a device that is not an allowlisted share-rooted redirector: one device can multiplex many mounts (WinFsp-style), so clamping at the device root could resolve into a sibling mount. Mup/LanmanRedirector keep the silent share-root clamp. - Restrict the null-RootDirectory decision in the three NtCreateFile openers to the shapes our producers emit (\??\ and \Device\ prefixes, one shared predicate) so other rooted strings no longer resolve in the raw object namespace. - Surface NameTooLong from the base queries as ENAMETOOLONG instead of EBADF, and make server-only UNC volume-relative names fail closed with or without a trailing separator. - Test matrix for the uncommon shapes: volume-root bases, redirector and lookalike device names, buffer-boundary lengths, non-file dirfds, drive-qualified passthrough, and classifier component-edge cases.
cargo test for bun_sys and bun_paths never linked: the crates reference mimalloc, simdutf, highway, libuv and a handful of runtime symbols that only the full binary link provides. Add a dev-only bun_test_native_link crate whose build script archives the already-built libuv objects from build/debug and provides the remaining symbols as shims — functional Rust implementations where tests actually execute them (allocator, transcoding, scalar string classification), loud panic stubs for the WTF/JSC-backed surfaces tests must never reach. Wired in as a dev dependency of the two crates only; the product build is unchanged. Also initializes cached stdio in the one test that relied on the binary's startup having done it.
The subtraction boundary must be a real device prefix: accept only \Device\<name…> in the non-allowlisted arm, so a rename landing between the two name queries can no longer shear the boundary to bare \Device and hand `..` a forged depth budget (deeper shears only tighten the clamp and stay accepted). The floor-crossing refusal now surfaces as EINVAL — it is a path-shape decision, not descriptor state; the two name-query failures keep EBADF. Leading-colon components flow through again instead of tripping a ported sanity assert: the dirfd join can legitimately collapse to a `\:\…` object name, which NtCreateFile rejects with a proper error, so the output-side debug assert in the normalizer is removed (the input-side caller-mistake assert stays) and stream-name shapes behave as before throughout. Tests derive the clamp floor behaviorally instead of counting device components, and cover forward-slash traversal, exact-floor landings, dot-component neutrality, and the colon flow-through matrix. All of them execute now.
|
Re the repeated bare-name passthrough suggestion (third occurrence): see the investigation above (#33874 (comment)) — the shape is unreachable from user code and the proposed change would introduce a third semantics matching neither Win32 nor main. The asymmetry is now pinned by the |
There was a problem hiding this comment.
The trailing-separator fix in def741b looks right (and the whole_base gate correctly preserves the load-bearing \ on bare device names). No new issues found on this revision, but the core change — NT device-path construction for every dirfd-relative open, new ..-clamp/floor semantics, and the ~800-line bun_test_native_link shim crate — is substantial enough that someone familiar with the Windows syscall layer should sign off.
Extended reasoning...
Overview
This PR reworks how Windows openat builds object names for NtCreateFile: instead of resolving the base directory handle to a DOS drive-letter path (\\??\\C:\\…) via a mount-manager IOCTL, it queries the NT device path (\\Device\\HarddiskVolumeN\\…) directly and constructs the object name from that. The change spans src/sys/lib.rs (normalize_path_windows_opts, nt_clamp_prefix_len, is_nt_object_name, and the RootDirectory selection in three NtCreateFile wrappers), a new one-pass path classifier in src/paths/classify.rs, a VolumeName::None variant in src/windows_sys/externs.rs, a removed debug assertion in src/paths/resolve_path.rs, and a new src/test_native_link crate (~810 lines of dev-only native-symbol shims + a build script that archives prebuilt libuv objects) so cargo test -p bun_sys/bun_paths link standalone. ~600 lines of new Windows-gated unit tests cover the clamp boundary, normalization output shapes, and end-to-end NtCreateFile opens.
Security risks
Path handling for .. traversal is the security-relevant surface here. The PR tightens it in most respects: the device/share prefix is copied outside the normalizer's reach so excess .. structurally cannot eat into \\Device\\… or \\server\\share; on non-allowlisted devices a .. that would cross the floor now returns EINVAL instead of silently clamping; and the subtraction boundary is only accepted when it lands on a real \\Device\\<name> prefix (guarding against rename-race shears). It also removes the drive-letter re-resolution TOCTOU where a per-session subst could retarget an fd-relative open. That said, the nt_clamp_prefix_len allowlist (Mup, LanmanRedirector) and the depth-walk in normalize_path_windows_opts are new, non-trivial logic on a path that every dirfd-relative Windows open flows through — the reasoning is well-documented and unit-tested, but it warrants human eyes.
Level of scrutiny
High. This is core Windows syscall-path infrastructure: normalize_path_windows feeds every multi-component openat (tarball extraction, node:fs dirfd ops, shell file operations). The PR also changes user-observable behavior in edge cases (.. across a folder-mount root now fails EINVAL instead of following the drive-letter parent; network-redirector error shades may differ), removes a debug_assert! in the shared normalizer, and introduces a whole new dev-only crate with a build script and hundreds of lines of #[no_mangle] allocator/simdutf/highway shims. None of this is mechanical.
Other factors
I flagged one bug on the previous revision (trailing \\ when rel collapses to nothing); the author fixed it in def741b with a more precise gate than I suggested (whole_base distinguishes directory-path prefixes from bare device names, where the trailing separator selects root-directory vs. volume-device) and pinned both sides with tests. The author also gave detailed, well-reasoned responses to the auto-issue-finder and the C:foo passthrough suggestion. Test coverage for the new logic is thorough. The bug-hunting pass on the current revision found nothing. Still, the scope, the behavioral changes noted in the PR description, and the new test-link infrastructure put this well outside the auto-approval bar.
…ocally" This reverts commit ba6415a900686e0212b52a12ffb62b56900e78a9 and removes the test module registrations that referenced it. The local test-linking infrastructure does not belong in this change.
| // device roots: copy the device prefix (the NT object name minus the | ||
| // volume-relative name) verbatim and normalize only the remainder. | ||
| let mut rel_buf = bun_paths::w_path_buffer_pool::get(); | ||
| let vol_rel = match windows::GetFinalPathNameByHandle( |
There was a problem hiding this comment.
Two system calls here? Do we really need this?
There was a problem hiding this comment.
Narrowed in 05b7c5f. The second call was already gated to ..-containing paths (never the hot path), and it's now confined to the minimal set: paths that resolve above the directory handle (readdir("..") and friends). Everything else — multi-component opens, dotted names, within-tree .. like a\..\b — is exactly one handle query; the within-tree case can't reach any clamp floor because the base's NT name is kernel-normalized, so no boundary is needed and the output is provably identical.
For the residue it can't be removed: resolving .. above the handle needs to know where the volume (or network mount) root sits, and that boundary isn't derivable from the NT name alone — device prefixes aren't a fixed shape (\Device\HarddiskDmVolumes\<x>\Volume1, and WinFsp-style redirectors multiplex many mounts under one device, where a guessed floor would let excess .. walk into a sibling mount). FileNameInformation is the one handle-local, mount-manager-free source of that fact. Verified at the fs surface: readdirSync("a/../b") issues zero volume-relative queries, readdirSync("..") exactly one.
Jarred-Sumner
left a comment
There was a problem hiding this comment.
Can we avoid the added syscall?
The second GetFinalPathNameByHandle call exists to place the clamp
floor, and the floor is only reachable when the relative part resolves
above the directory handle itself: the base suffix is kernel-normalized
so within-tree `..` (a\..\b) can never dip below its own start. Teach
classify_rel_t to report whether the running component depth ever goes
negative, computed in its existing single pass, and gate the second
query, the boundary subtraction, and the floor walk on that instead of
mere `..` presence. Multi-component opens, dotted names, and within-tree
`..` now cost exactly one handle query; only paths that resolve above
the directory (readdir("..") and friends) pay the second, where the
volume boundary is genuinely required and cannot be derived from the
NT name alone.
Also drops a leftover comment in the normalizer from the earlier assert
removal.
The gate switch left RelPathFacts.has_dotdot_component with no production reader — remove the field (the depth scoring it shared with climbs_above_start stays). Add normalize-level tests for the fast path's collapsed-to-base shape and for a climbing relative with a leading name component, which keeps the floor walk covered now that within-tree dotdots skip it.
|
Merged main (85aa006) to pick up ed4a810 (#33898). The Windows hard failure on the previous build (71563, |
#33874 removed the debug_assert block that consumed `result`, leaving a clippy::let_and_return violation that every open PR now trips. The code is identical on main; this is merge hygiene.
…frames) (#33951) ## Problem `cargo clippy` fails on `main`, and therefore on **every open pull request that touches a `.rs` file**. There are *two* breakages, stacked — the second was hidden because `bun_runtime` depends on `bun_paths`, so it was never checked while `bun_paths` failed to compile. ### 1. `bun_paths` — `clippy::let_and_return` ``` error: returning the result of a `let` binding from a block --> src/paths/resolve_path.rs:1148:5 ``` #33874 removed the `debug_assert` that used to sit between the binding and the return, leaving `let result = ...;` followed by `result`. ### 2. `bun_runtime` — `clippy::large_stack_frames` ``` error: this function may allocate 221049 bytes on the stack --> src/runtime/jsc_hooks.rs:301:11 ``` Introduced by #31216. The frame is `bun_bundler::Transpiler::init`'s return value: a `Transpiler` carries a `Resolver` whose `BSSMapInner<DirInfo, 2048>` is ~229 KB, and it is returned by move then `ptr::write`n straight into `(*vm).transpiler`. Optimized builds elide the copy; the function runs once per VM init. Only Linux crosses the 128 KB `stack-size-threshold` from `clippy.toml`, which is why local macOS clippy never flagged it. ## Why neither was caught `.github/workflows/clippy.yml` triggers on `workflow_call`, `workflow_dispatch`, `pull_request` and `merge_group` — there is **no `push:` trigger**, so **main never lints itself**. Because `pull_request` lints the PR merged into main, both breaks surface on contributors' PRs instead, attributed to their commits. ## Fix 1. Return the slice directly. The binding had no other use. 2. Suppress `large_stack_frames` on `init_runtime_state` with the reason recorded, exactly as `find_context_identifiers::walk_expr` already does. `#[allow]` rather than `#[expect]` because the lint does not fire on macOS, where an expectation would go unfulfilled. Removing the temporary for real needs an in-place `Transpiler::init_in_place(dst, ..)` — a larger change owned by the bundler, and worth doing separately now that every Worker thread runs this init. ## Verification - Reproduced **both** failures on a clean `main` tip before fixing; `cargo clippy -p bun_paths --no-deps` is clean after (1), and `cargo check -p bun_runtime` + `cargo fmt --check` are clean after (2). CI's ubuntu clippy job is the oracle for (2), since macOS never fires it. - Built `bun-debug` and drove the surface `normalize_string_generic_tz` actually backs, against a built node v26.3.0 — identical on every case: ``` a/b/../c => "a/c" /a/./b//c/ => "/a/b/c/" ../../x => "../../x" resolve => "/foo/bar/baz" resolve-up => "/foo/baz" win32 normalize => "C:\a\c" win32 UNC resolve => "\\server\share\b" normalize("") => "." normalize(".") => "." normalize("/") => "/" ``` - `bun bd test test/js/node/path/` → 122 pass, 2 skip, 0 fail. All 16 vendored `test-path*.js` pass. ## Notes A workspace-wide `cargo clippy --workspace --no-deps --keep-going` also reports `derivable_impls` for `Sendfile` in `src/runtime/server/FileResponseStream.rs`, but only on macOS: `socket_fd: Fd::INVALID` is a Linux/Android-gated field that is not `Default`, so the impl is genuinely not derivable on the Ubuntu runner. Left alone. Adding a `push:` trigger to `clippy.yml` would stop this class of breakage from being invisible on main. Happy to do that here or in a follow-up.
What
On Windows, opening a path relative to a directory handle (
openatwith a multi-component subpath — tarball extraction, dirfd-relativenode:fsops, shell file operations) builds one absolute name forNtCreateFile. That name used to be\??\C:\base\rel, with the base resolved viaGetFinalPathNameByHandleW(VOLUME_NAME_DOS). This PR switches the base query toVOLUME_NAME_NTand emits the\Device\HarddiskVolumeN\base\relobject name directly.The drive letter was only ever decoration for the kernel's benefit: the output feeds
NtCreateFileexclusively (verified across every caller), and the kernel resolves\??\C:back to the device anyway.Performance
Every route through this function is faster than or equal to main (release build, Windows 11, 200k iterations):
..): ~22.1 µs → 12.1–15.1 µs per call (one handle-local query instead of the mount-manager round trip).readdir("..")-class): ~22.1 µs → 17.3 µs — these pay one extra handle-local query, because the volume/mount boundary they need is not derivable from the NT name; the audit of every caller shows only upward directory listings,..-leading globs, andfile:../monorepo installs take this path.Why
The DOS translation is the one step of this path with real costs:
IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH, a round-trip through a single device queue). The NT form is answered from the handle itself. Benchmarked function-for-function (release build, Windows 11, 200k iterations on the same directory handle with alib\mod\file.js-shaped input; the old body reconstructed verbatim from the pre-change commit): ~22.1 µs/op before → 12.1–15.1 µs/op after, 32–45% faster. The rare..path — a second handle query plus the containment walk — costs ~17.3 µs/op, still below the old fast path; the bare-name passthrough is syscall-free at ~10 ns. Traced during a 200-file tarball install: 262 mount-manager IOCTLs before, 6 after (the remainder come fromfd→pathstring helpers, not this path).uv_fs_realpath's libuv docs and std::fs::canonicalize uses GetFinalPathNameByHandleW, which can fail rust-lang/rust#79449 document the failure class — and sandboxed (AppContainer) tokens are denied the mount-manager query outright (see Fix WeaklyCanonicalPath ERROR_ACCESS_DENIED in Windows AppContainers microsoft/onnxruntime#28509 for the same diagnosis). Those cases previously failed withEBADF; they now open fine.subst/net useshadowing a global letter — including via an impersonated token's device map — could silently retarget an fd-relative open to a different volume after the handle was opened. The NT form pins the volume the handle actually lives on, which is what openat semantics require.Bugs fixed (relative to main)
EBADF. They now open normally.\??\C:form re-resolved the letter through the caller's device map on every open, so a per-sessionsubst/net useshadowing the letter after a directory handle was opened (including via an impersonated token's device map) pointed later fd-relative opens at a different volume. Opens are now pinned to the volume the handle actually lives on.How
FileNameInformation) runs only when the relative part actually resolves above the directory handle — a single-pass classifier reports whether the running component depth ever goes negative; within-tree..likea\..\bprovably never reaches any clamp floor and takes the single-query path with identical output...clamp extends past\server\share, matching how the old drive-letter form clamped at the share. The prefix is copied verbatim and only the remainder is normalized; the shared normalizer is untouched, since it serves user-facingpathsemantics where\Devicemust not be special.open_file_at_windows_nt_pathdecidedRootDirectoryby testing for a literal\??\prefix; it now uses the same leading-separator rule as its dir/stat siblings.Behavior notes
..climb floor is now structural: the volume prefix (share root, for UNC) is copied outside the normalizer's reach, so excess..can never eat into the device/server/share components. On main, the shared normalizer's floor was absent for UNC prefixes in this branch — not reachable from user-supplied paths (those are collapsed lexically upstream), but internal..uses are now clamped by construction, and a boundary that can't be established fails loud instead of guessing...that would cross the clamp floor now fails withEINVALinstead of silently clamping: a single device can multiplex many mounts (WinFsp-style sshfs/rclone, WebDAV), so clamping at the device root could resolve into a sibling mount. The same applies to folder-mounted volumes —..across the mount root previously followed the drive-letter parent, which cannot be derived from the handle without the mount manager, so it now fails loudly rather than guessing. Win32 consumers of the flag-selected output receive the\\?\GLOBALROOTspelling rather than a raw NT name.\Device\<name>prefix, so a rename racing the two name queries can never shear the..clamp above the device (deeper shears only tighten it). Path-vs-open TOCTOU beyond that is inherent to every path-based syscall and unchanged from main.ERROR_BAD_NETPATHvsERROR_BAD_NET_NAMEshades differently, since explicit-redirector names skip MUP prefix resolution.RootDirectory(literal\??\prefix vs leading separator); they now share the leading-separator rule. Not user-observable — no caller delivers a rooted path here un-absolutized — but the two paths no longer disagree.Testing
..into parent, excess-..clamp,., bare component passthrough, absolute branch unchanged), plus an integration test that opens and reads a file throughopen_file_at_windows_awith the new name shape.\Device\…names.test/js/node/fsandtest/js/bun/shellsuites: every failure reproduces byte-identically on an unmodified main build of the same machine (pre-existing local issues).