cli: refuse to start when the cwd is longer than PATH_MAX instead of using the executable's directory
RO
2mo ago+159+159+012345678901234567890123456789-43-43-01234567890123456789
Problem
bun x.cjsstarted from a working directory whose absolute path is longer than PATH_MAX (4096 bytes on Linux, 1024 on macOS) runs<directory containing the bun binary>/x.cjsand exits 0; thex.cjsin the real cwd is ignored. With-e,require("./mod.cjs"),path.resolve()andBun.mainall point at the binary's directory whilefs.readFileSync("mod.cjs")reads the real cwd. No warning is printed.- Cause:
bun_core::getcwd_or_exe_dir(src/bun_core/util.rs:4140) substituted the executable's directory on everygetcwdfailure, andArguments::parse(src/runtime/cli/Arguments.rs:867) seededtop_level_dirfor run/auto commands from it. The fallback exists for a cwd that was deleted (ENOENT), butgetcwdalso fails with ERANGE (glibc, macOS) or ENAMETOOLONG (musl) when the cwd's path does not fit in the 4096-byte buffer, and that directory still exists. - Node refuses to start in that situation (
ERANGE: process.cwd failed with error result too large, uv_cwd, exit 1). Bun's non-runtime commands already refused too, but witherror: An internal error occurred (ERANGE).
Fix
getcwd_or_exe_dirfalls back to the executable's directory only for ENOENT and returns every other failure;getcwd_lennow maps errno instead of collapsing everything toUnexpected. The[eval]/[stdin]/cron/feedback callers inrun_command.rspropagate the error with?.Arguments::parsedoes the same for the startup cwd: ENOENT on a run/auto/node command keeps the exe-dir fallback (bun_core::self_exe_dir, the old fallback body split out); anything else, and a relative--cwdwithout a usable base, printsERANGE: Numerical result out of range: Could not get the current working directory (getcwd)and exits 1. This is the same error path--cwdalready uses for a failedchdir, sobun test/bun buildget the readable message as well.- Correct because the substituted directory is never what the user's relative paths mean: a deleted cwd has no files for them to refer to (and Node boots there too, which
test/js/node/test/parallel/test-cwd-enoent*.jspin), while an over-long cwd does, so the only safe answer is to refuse. - Verified with
test/cli/run/run_command.test.ts: fivebun ...invocations (-e,x.cjs,run x.cjs,--cwd . x.cjs,test) from a cwd longer than PATH_MAX must print that error and exit 1; they fail on the current release build (-eexits 0 printing the exe dir,x.cjsandrun x.cjsprintModule not found "x.cjs",--cwd .andtestprint the internal-error line) and pass with this change. A sixth test pins thatbun -estill boots from a deleted cwd andprocess.cwd()throws ENOENT there; it passes before and after. - Also ran the four
test-cwd-enoent*.jsnode tests,test/cli/run/run-eval.test.tsandtest/cli/run/as-node.test.tsagainst the debug build; all pass. The original repro (bun hardlinked next to a plantedx.cjs, cwd of 4300 bytes) now prints the error instead of running the planted file, and a 4000-byte cwd still runs the real file.
Background
- PATH_MAX bounds the length of a path passed to a single syscall, not how deep a process can be:
chdirone component at a time works indefinitely, butgetcwdhas to return the whole path and fails once it does not fit. Bun'sPathBufferis exactly PATH_MAX bytes, so such a cwd cannot be represented anywhere in the resolver, and startup is the only place to reject it. top_level_diris the directory the runtime resolves the entry point, relativerequire/importandpath.resolve()against. It is captured once at startup;process.cwd()does a freshgetcwdon every call, which is why it reported ERANGE while everything else used the exe dir.- The exe-dir fallback mirrors Node's
Environment::GetCwd, which substitutes the executable's directory whenuv_cwdfails so thatnode -e,-rand the REPL still work after the shell's cwd is deleted; Node's own relative-script path fails regardless becausepath.resolvecallsprocess.cwd().
Not changed here
bun install/bun pm/bunxdo not go through this block; they already refuse an over-long cwd viaFileSystem::init(None)with the genericAn internal error occurred (ERANGE)line, unchanged.- A deleted cwd plus a relative script (
cd dir; rmdir dir; bun x.cjs) still resolves against the executable's directory, as before: that is the ENOENT fallback working as designed. Node errors there instead; tightening that is a separate decision. - A cwd within a few bytes of PATH_MAX (4084..4095 on Linux) panics earlier in bunfig loading, and
process.chdir()into a directory whose path is exactly PATH_MAX-1 bytes panics inset_process_cwd; both are tracked separately. - #32357 (stale) maps the same ENOENT to a dedicated error variant for the generic root-error message; if it is revived, its mapping and the
FileNotFoundmatch here need to agree.
no test proof · iteration 0 · Platform-specific test(s) that do not run on this machine. Deferring to CI, which covers all platforms: test/cli/run/run_command.test.ts