resolver: bound load_as_file path before writing into its PathBuffer
Problem
Any absolute import specifier at or past ~4092 bytes aborts the process instead of throwing MODULE_NOT_FOUND.
const p = "/" + "a".repeat(5000) + ".ts";
try { require.resolve(p) } catch (e) { console.log("ERR", e.code) }
console.log("SURVIVED");
panic: range end index 5004 out of range for slice of length 4096
Every public entry that reaches the module resolver hits it: require(p), require.resolve(p), await import(p), Bun.resolveSync(p, cwd), new Worker(p), Bun.build({entrypoints:[p]}), and bun /aaa...ts on the CLI. A 4091-byte path throws cleanly; 4092 aborts. Byte length governs (a 1404-char / 4204-byte €-filled path also aborts).
Cause
load_as_file copies the incoming path into a fixed MAX_PATH_BYTES PathBuffer before probing extensions:
bufs!(load_as_file)[..path.len()].copy_from_slice(path);
When the specifier's dirname names a real directory (e.g. /), read_directory succeeds and the copy runs with an arbitrarily long path. load_extension then slices [0..path.len() + ext.len()] from the same buffer, so a path that fits on its own still overflows once an extension is appended.
The directory probe that runs after load_as_file gives up has the same shape. load_as_index_with_browser_remapping appends SEP and a NUL to the directory path in a PathBuffer, which writes one byte past the end for a directory at MAX_PATH_BYTES - 1 (the longest path the OS allows). Under target: "browser" it also joins the directory with "index" through the unchecked abs_buf, which panics for directories at MAX_PATH_BYTES - 2 through - 5.
Fix
src/resolver/resolver.rs:
load_as_file: returnNonewhenpath.len() >= MAX_PATH_BYTES.>=rather than>because the.jsto.tsxrewrite grows the path by one byte.load_extension: returnNonewhenpath.len() + ext.len() > MAX_PATH_BYTES.load_as_index_with_browser_remapping: returnNotFoundwhen the path plusSEPplus NUL does not fit, and use the existingabs_buf_checkedfor the"index"join.
Tests
test/js/bun/resolve/resolve-error.test.ts gains a describe with eight cases:
- a path well past
MAX_PATH_BYTESthroughrequire.resolve,import(), andBun.resolveSync(theload_as_filecopy), - a path at
MAX_PATH_BYTES - 4so only the appended extension overflows (theload_extensionslice), - a
.jspath at exactlyMAX_PATH_BYTES(pins the>=: weakening it to>fails this test and nothing else), - a multibyte basename past
MAX_PATH_BYTES, - a real directory created at
MAX_PATH_BYTES - 1(theSEP+ NUL append), - a real directory at
MAX_PATH_BYTES - 2underBun.build({ target: "browser" })with abrowsermap (the"index"join).
Each spawned child aborts with a range end index or index out of bounds panic on current main and exits 0 with MODULE_NOT_FOUND / a ResolveMessage with the fix. The two directory cases skip on Windows, where MAX_PATH_BYTES is far past what the filesystem allows.
Notes
The first revision also tightened an off-by-one in dir_info_cached_maybe_log (> guarding a [..len + 1] slice). #36165 landed the same change on main independently, so that hunk dropped out in the rebase.
The two index-probe sites were found in review after the rebase. The SEP + NUL one needs a directory whose absolute path is exactly one byte under the OS limit, so it is much harder to reach than the load_as_file copy, but it goes through the same public entry points. Boundary sweep on linux after the fix: every length from 4076 to 6145 through require.resolve, and directories at MAX - 1 through MAX - 20 through both import() and browser-target Bun.build, return a normal error.
Also ran test/js/bun/resolve/resolve.test.ts and test/bundler/bundler_browser.test.ts.
no test proof · iteration 2 · Platform-specific test(s) that do not run on this machine. Deferring to CI, which covers all platforms: test/js/bun/resolve/resolve-error.test.ts