From 9efd8c3b1642c64f85515ac31134463bfe44d861 Mon Sep 17 00:00:00 2001 From: robobun Date: Fri, 24 Apr 2026 05:27:51 +0000 Subject: [PATCH 1/2] Fix Bun.pathToFileURL crash with relative paths longer than PATH_MAX ResolvePath__joinAbsStringBufCurrentPlatformBunString used the 4096-byte threadlocal join_buf as the output buffer for normalization. When the input relative path (joined with cwd) exceeded 4096 bytes, normalizeStringGenericTZ would index past the end of the buffer. Use a stack-fallback allocator sized to cwd + input instead, matching Node.js which returns a file URL for arbitrarily long paths. --- src/resolver/resolve_path.zig | 14 ++++++++++++-- test/js/bun/util/fileUrl.test.js | 12 ++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/resolver/resolve_path.zig b/src/resolver/resolve_path.zig index a8077aecf6d..84222722e8d 100644 --- a/src/resolver/resolve_path.zig +++ b/src/resolver/resolve_path.zig @@ -2048,9 +2048,19 @@ export fn ResolvePath__joinAbsStringBufCurrentPlatformBunString( const str = in.toUTF8WithoutRef(bun.default_allocator); defer str.deinit(); + const cwd = globalObject.bunVM().transpiler.fs.top_level_dir; + + // The input is user-controlled and may be arbitrarily long. The + // threadlocal `join_buf` is only 4096 bytes, so use a stack-fallback + // allocator that heap-allocates for oversized inputs. + var sfa = std.heap.stackFallback(bun.MAX_PATH_BYTES * 2, bun.default_allocator); + const alloc = sfa.get(); + const buf = bun.handleOom(alloc.alloc(u8, cwd.len + str.slice().len + 2)); + defer alloc.free(buf); + const out_slice = joinAbsStringBuf( - globalObject.bunVM().transpiler.fs.top_level_dir, - &join_buf, + cwd, + buf, &.{str.slice()}, .auto, ); diff --git a/test/js/bun/util/fileUrl.test.js b/test/js/bun/util/fileUrl.test.js index 8808a9422e4..a9bcd96a9b2 100644 --- a/test/js/bun/util/fileUrl.test.js +++ b/test/js/bun/util/fileUrl.test.js @@ -6,6 +6,18 @@ describe("pathToFileURL", () => { it("should convert a path to a file url", () => { expect(pathToFileURL("/path/to/file.js").href).toBe("file:///path/to/file.js"); }); + + it("should handle relative paths longer than PATH_MAX", () => { + const long = Buffer.alloc(6000, "a").toString(); + const url = pathToFileURL(long); + expect(url.href.endsWith("/" + long)).toBe(true); + }); + + it("should normalize long relative paths with .. segments", () => { + const input = Buffer.alloc(14000, "abcdef/").toString() + Buffer.alloc(6000, "../").toString() + "final"; + const url = pathToFileURL(input); + expect(url.href).toBe(`${pathToFileURL(process.cwd())}/final`); + }); }); describe("fileURLToPath", () => { From 31a2c880520497d8ea90529e9d9fedf8b0360da3 Mon Sep 17 00:00:00 2001 From: robobun Date: Fri, 24 Apr 2026 05:47:14 +0000 Subject: [PATCH 2/2] Use 4096-byte stackFallback inline buffer bun.MAX_PATH_BYTES * 2 is ~192KB on Windows. Use 4096 bytes of inline stack (same as the previous threadlocal join_buf) and heap-allocate when cwd + input exceeds that. --- src/resolver/resolve_path.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/resolver/resolve_path.zig b/src/resolver/resolve_path.zig index 84222722e8d..28aafc48d65 100644 --- a/src/resolver/resolve_path.zig +++ b/src/resolver/resolve_path.zig @@ -2053,7 +2053,7 @@ export fn ResolvePath__joinAbsStringBufCurrentPlatformBunString( // The input is user-controlled and may be arbitrarily long. The // threadlocal `join_buf` is only 4096 bytes, so use a stack-fallback // allocator that heap-allocates for oversized inputs. - var sfa = std.heap.stackFallback(bun.MAX_PATH_BYTES * 2, bun.default_allocator); + var sfa = std.heap.stackFallback(4096, bun.default_allocator); const alloc = sfa.get(); const buf = bun.handleOom(alloc.alloc(u8, cwd.len + str.slice().len + 2)); defer alloc.free(buf);