From b1d0aa86a43c921d5d54c82df0117f4c9c00be2c Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Tue, 5 May 2026 10:34:02 +0000 Subject: [PATCH] Fix crash in Bun.file().writer() with invalid path/fd options When the options object passed to .writer() contains a non-string path or a non-integer fd, streams.Start.fromJSWithTag returns an .err variant. The caller unconditionally accessed the .FileSink field afterward, triggering a union-tag safety panic in debug builds and undefined behavior in release builds. Handle the .err case by throwing a proper JS error, and deinit the allocated path from fromJSWithTag before overwriting it with the blob's own path. --- src/runtime/webcore/Blob.zig | 14 +++++++++++++- test/js/bun/util/filesink.test.ts | 7 +++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/runtime/webcore/Blob.zig b/src/runtime/webcore/Blob.zig index 6cf5c0475545..a823bae41671 100644 --- a/src/runtime/webcore/Blob.zig +++ b/src/runtime/webcore/Blob.zig @@ -2975,7 +2975,19 @@ pub fn getWriter( if (arguments.len > 0 and arguments.ptr[0].isObject()) { stream_start = try jsc.WebCore.streams.Start.fromJSWithTag(globalThis, arguments[0], .FileSink); - stream_start.FileSink.input_path = input_path; + switch (stream_start) { + .err => |err| { + sink.deref(); + return globalThis.throwValue(try err.toJS(globalThis)); + }, + .FileSink => |*file| { + file.input_path.deinit(); + file.input_path = input_path; + }, + else => { + stream_start = .{ .FileSink = .{ .input_path = input_path } }; + }, + } } switch (sink.start(stream_start)) { diff --git a/test/js/bun/util/filesink.test.ts b/test/js/bun/util/filesink.test.ts index 736e5b085731..ad1c8cb0beac 100644 --- a/test/js/bun/util/filesink.test.ts +++ b/test/js/bun/util/filesink.test.ts @@ -207,6 +207,13 @@ it("write result is not cumulative", async () => { await util.promisify(fs.close)(fd); }); +it.skipIf(isWindows)("writer() throws on invalid options instead of crashing", () => { + const file = Bun.file(path.join(tmpdirSync(), "test.txt")); + expect(() => file.writer({ path: 123 })).toThrow(expect.objectContaining({ code: "EINVAL" })); + expect(() => file.writer({ fd: "not a number" })).toThrow(expect.objectContaining({ code: "EBADF" })); + expect(() => file.writer({ fd: -9999.5 })).toThrow(expect.objectContaining({ code: "EBADF" })); +}); + if (isWindows) { it("ENOENT, Windows", () => { expect(() => Bun.file("A:\\this-does-not-exist.txt").writer()).toThrow(