Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 5 additions & 17 deletions src/runtime/node/node_fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,9 +218,6 @@ pub use super::node_fs_binding::Binding;
use bun_jsc::JSPromiseStrong;

use super::dir_iterator as DirIterator;
#[cfg(not(windows))]
use bun_resolver::fs::FileSystem;

// On POSIX the libuv-backed code paths (`UVFSRequest`, `uv_fs_*`) are absent:
// `UVFSRequest` aliases `AsyncFSTask` and every `uv::*` reference is gated
// behind `#[cfg(windows)]`. There is intentionally **no** POSIX stub module
Expand Down Expand Up @@ -7385,27 +7382,18 @@ impl NodeFS {
{
let mut outbuf = bun_paths::path_buffer_pool::get();
let inbuf = &mut self.sync_error_buf;
// SAFETY: single-threaded init flag (resolver/fs.rs).
debug_assert!(
bun_resolver::fs::INSTANCE_LOADED.load(core::sync::atomic::Ordering::Relaxed)
);

let path_slice = args.path.slice();
// SAFETY: instance() returns the leaked singleton; INSTANCE_LOADED checked above.
let fs = FileSystem::get();
let parts = [fs.top_level_dir, path_slice];
let inbuf_len = inbuf.len();
let Some(joined) = fs.abs_buf_checked(&parts, &mut inbuf[..inbuf_len - 1]) else {
if path_slice.len() >= inbuf.len() {
return Err(sys::Error {
errno: E::ENAMETOOLONG as _,
syscall: sys::Tag::realpath,
path: args.path.slice().into(),
..Default::default()
});
};
let path_len = joined.len();
inbuf[path_len] = 0;
let path = ZStr::from_buf(&inbuf[..], path_len);
}
// Let the OS walk the original components. Resolving to an absolute
// path first would collapse `..` before a preceding symlink is followed.
let path = args.path.slice_z(inbuf);

#[cfg(any(target_os = "linux", target_os = "android"))]
let flags = sys::O::PATH; // O_PATH is faster
Expand Down
21 changes: 21 additions & 0 deletions test/js/node/fs/fs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3235,6 +3235,27 @@ it.if(isPosix)("realpathSync resolves root, regular files, and symlinks", () =>
expect(realpathSync(linkPath)).toBe(self);
});

it.if(isPosix)("realpath resolves a symlink before a following parent traversal", async () => {
using dir = tempDir("fs-realpath-symlink-parent", {});
const root = String(dir);
const actualDir = join(root, "actual");
const nestedDir = join(actualDir, "nested");
const expected = join(actualDir, "target.txt");
const collision = join(root, "target.txt");
const linkPath = join(root, "link");
mkdirSync(nestedDir, { recursive: true });
writeFileSync(expected, "expected");
writeFileSync(collision, "collision");
symlinkSync(nestedDir, linkPath);
const input = `${linkPath}${path.sep}..${path.sep}target.txt`;

expect(realpathSync(input)).toBe(expected);
expect(realpathSync.native(input)).toBe(expected);
expect(await promises.realpath(input)).toBe(expected);
expect(await promisify(fs.realpath)(input)).toBe(expected);
expect(await promisify(fs.realpath.native)(input)).toBe(expected);
});

// src/sys/sys.zig getFdPath has an exhaustive per-OS switch: .windows
// (GetFinalPathNameByHandle), .mac (F_GETPATH), .linux (/proc/self/fd, also
// covers Android), .freebsd (fcntl F_KINFO + struct_kinfo_file). On every
Expand Down