bunfig: stop panicking when the config path does not fit in a path buffer
Merged
RO
2mo ago+270+270+012345678901234567890123456789-47-47-01234567890123456789
Problem
bun x.cjs,bun -e 0and every command inALWAYS_LOADS_CONFIG(bun test,bun build,bun install,bun pm, ...) crash at startup when the working directory is 4084..4095 bytes long on Linux (1012..1023 on macOS, whereMAX_PATH_BYTESis 1024):- cwd of exactly
MAX_PATH_BYTES - 12bytes:panic: index out of bounds: the len is 4096 but the index is 4096 - longer:
panic: range end index 4101 out of range for slice of length 4095(end index = cwd length + 11), top framebun_paths::resolve_path::normalize_string_generic_tz(src/paths/resolve_path.rs:1071), called frombun_bunfig::arguments::load_config
- cwd of exactly
- The same panics happen for
--config=<value>when the value (absolute) or cwd + value (relative) isMAX_PATH_BYTESbytes or longer, and forbun install/bun pm ...when$XDG_CONFIG_HOME(or$HOME) isMAX_PATH_BYTES - 13bytes or longer. - Cause, all in
src/bunfig/arguments.rs:load_configjoined cwd + config name into a stackPathBufferwith the uncheckedjoin_abs_string_buf(L213) and wrote the NUL atconfig_buf[len](L217). A result of exactlyMAX_PATH_BYTESbytes fits the join but the NUL write is out of bounds; anything longer overflows inside the join.- The absolute
--configarm copied the argument into the buffer without a length check (L192). get_home_config_pathjoined$XDG_CONFIG_HOME/$HOME+.bunfig.tomlwith the uncheckedjoin_abs_string_buf_z(L24, L30), which writes the NUL itself.
- Reproduced on the release build of main. The cwd case only needs
bun -e 0from a directory of that length: the path is built before anything is opened, so no bunfig.toml has to exist.
Fix
join_config_pathbuildsdir/namewithjoin_abs_string_buf_checkedintobuf[..len - 1](reserving the NUL slot) and returnsNonewhen the result does not fit; the cwd join andget_home_config_pathboth use it. The absolute--configarm length-checks and usesresolve_path::z, the guard-then-zshape its other callers use.- A path that does not fit takes the existing "config failed to open" route in
load_bunfig, factored intounreadable_config: auto-loaded configs (bunfig.tomlin the cwd, the global.bunfig.toml) are skipped, as they already are on any read error; an explicit--configexits 1 withENAMETOOLONG: <path>: File name too long (open())/while reading config "<path>", the message a kernel ENAMETOOLONG on the same flag already produces. - Why this is correct:
open()rejects any path ofMAX_PATH_BYTESbytes or more (bun_sys::openat_areturns ENAMETOOLONG for the same condition before the syscall), so no file at such a path could have been loaded; the failure now takes the existing route instead of overflowing the buffer.join_abs_string_buf_checkednormalizes before deciding, so a long--configvalue that normalizes to a path that fits still loads. Forbun <file>/bun -e,run_commandadditionally loadsbunfig.tomlrelative to the cwd whenload_configloaded nothing, so a bunfig.toml in such a deep directory is still picked up there. - The cwd is no longer also passed as the first
partsentry:join_abs_string_bufalready uses it as the base, so the result is identical, and the copy would have made the checked join count the cwd twice in its size estimate. - Verified with
test/config/bunfig/bunfig-errors.test.ts(15 new tests): on the unfixed build the 10 overflow cases fail with the panics above and the 5 boundary/normalization controls pass; with the fix all 20 tests in the file pass. Per site they cover the longest path that still loads (exactlyMAX_PATH_BYTES - 1bytes, asserted through the absolute path in the parse error), theMAX_PATH_BYTEScase that failed on the NUL write, and a longer one, forbun -eandbun file.jsin a deep cwd, relative and absolute--config, and$XDG_CONFIG_HOME/$HOMEthroughbun pm cache. Skipped on Windows, where the buffer (~96 KiB) is longer than any path, argument or environment value the OS accepts. - Also green with the fix:
test/config/bunfig/preload.test.ts,test/cli/install/bun-run-bunfig.test.ts,test/cli/bunfig-test-options.test.ts,test/cli/install/npmrc.test.ts, the global-bunfig tests intest/cli/install/minimum-release-age.test.ts;cargo clippy -p bun_bunfigandcargo fmtare clean. - Out of scope, reported separately:
bun install/bun pmwith$XDG_CONFIG_HOME/$HOMEofMAX_PATH_BYTES - 7bytes or longer still panic one step later in the.npmrclookup (src/install/PackageManager.rs, same unchecked join), so the global-config tests here stay below that length;--cwd <over-long value>(src/runtime/cli/Arguments.rs) andprocess.chdir()into a directory of exactlyMAX_PATH_BYTES - 1bytes are the same bug class at other sites. Working directories ofMAX_PATH_BYTESbytes or more fail ingetcwdbefore this code runs; #38363 covers those.
Background
PathBufferis bun's fixed stack buffer for path syscalls,MAX_PATH_BYTESlong (the platform'sPATH_MAX: 4096 on Linux, 1024 on macOS). Paths in it are NUL-terminated, so the longest path it holds isMAX_PATH_BYTES - 1bytes.resolve_path::join_abs_string_buf(cwd, buf, parts)ispath.resolveinto a caller buffer: it concatenates, normalizes and writes the result, assuming it fits.join_abs_string_buf_checkedis the variant for unbounded input and returnsNoneinstead of writing when the normalized result is longer thanbuf.resolve_path::zcopies a slice into aPathBufferwith a NUL and expects the caller to have checked the length.- bunfig loading:
load_configloadsbunfig.tomlfrom the cwd for the commands inALWAYS_LOADS_CONFIGand forbun <file>/bun -e(auto_loaded), or the--configvalue (not auto-loaded); install-family commands first load$XDG_CONFIG_HOME/.bunfig.toml, else$HOME/.bunfig.toml.load_bunfigignores read errors for auto-loaded configs and exits with the error for explicit ones.
no test proof · iteration 1 · Platform-specific test(s) that do not run on this machine. Deferring to CI, which covers all platforms: test/config/bunfig/bunfig-errors.test.ts