Skip to content

Blob.writer(): don't access .FileSink when Start.fromJSWithTag returns .err - #30254

Closed
robobun wants to merge 1 commit into
mainfrom
farm/0b1bd2d8/blob-writer-filesink-err
Closed

Blob.writer(): don't access .FileSink when Start.fromJSWithTag returns .err#30254
robobun wants to merge 1 commit into
mainfrom
farm/0b1bd2d8/blob-writer-filesink-err

Conversation

@robobun

@robobun robobun commented May 4, 2026

Copy link
Copy Markdown
Collaborator

What

Bun.file(path).writer({ path: <non-string> }) or .writer({ fd: <non-integer> }) would trip a debug-build safety check:

panic(main thread): access of union field 'FileSink' while field 'err' is active

Why

streams.Start.fromJSWithTag(..., .FileSink) returns a Start union. When the options object has a path that isn't a string, or an fd that isn't a valid integer, it returns the .err variant (EINVAL / EBADF). Blob.getWriter then unconditionally wrote to stream_start.FileSink.input_path, which is the wrong active union field.

Fix

Check the tag before accessing .FileSink. When .err is returned, clean up the sink and throw the error to JS (matching how sink.start() errors are already handled just below).

Found by Fuzzilli. Fingerprint: 5f85f9ffc48a209e

@github-actions github-actions Bot added the claude label May 4, 2026
@coderabbitai

coderabbitai Bot commented May 4, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

The PR fixes error handling in the non-Windows FileSink setup path within pipeReadableStreamToBlob by checking for errors from streams.Start.fromJSWithTag() and throwing them appropriately, and adds tests validating that invalid writer options throw the expected error codes.

Changes

FileSink Error Handling & Validation

Layer / File(s) Summary
Core Error Handling
src/runtime/webcore/Blob.zig
pipeReadableStreamToBlob now checks if streams.Start.fromJSWithTag(..., .FileSink) returns an error; on error it dereferences the sink and throws the converted JS error. The input_path assignment is now conditional on a .FileSink result instead of unconditional.
Tests
test/js/bun/util/filesink.test.ts
New test (skipped on Windows) verifies that Bun.file(path).writer() with invalid options throws expected error codes: EINVAL for non-string path, EBADF for invalid fd types or out-of-range values.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main fix: preventing unsafe union field access when Start.fromJSWithTag returns an error.
Description check ✅ Passed The description fully addresses both template sections: it explains what the bug is (union field access panic), why it happens (error path not checked), and includes verification details (test added for invalid options).
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


Review rate limit: 4/5 reviews remaining, refill in 12 minutes.

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/runtime/webcore/Blob.zig`:
- Around line 2982-2984: The assignment to stream_start.FileSink.input_path in
fromJSWithTag overwrites a previously allocated value and leaks; before setting
stream_start.FileSink.input_path = input_path, check if stream_start ==
.FileSink and if the existing stream_start.FileSink.input_path is
non-null/initialized, release it using the appropriate deinit/free for that type
(e.g., call the allocator free or the value's deinit method), then assign the
new input_path; ensure you use the same allocator/cleanup method used when the
path was allocated to avoid double-free issues.

In `@test/js/bun/util/filesink.test.ts`:
- Line 223: Replace the use of tmpdirSync() when constructing the test path with
the test harness tempDir fixture: import tempDir from the test harness at top of
the file, call tempDir() within the test to get a temporary directory, and use
path.join(tempDir(), "test.txt") in place of path.join(tmpdirSync(),
"test.txt"); ensure any existing tmpdirSync or fs.mkdtempSync import/usages are
removed so cleanup is handled by the harness.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 28371a33-2681-4c95-9208-25a2384a1579

📥 Commits

Reviewing files that changed from the base of the PR and between 0a7bed5 and 27bce8f.

📒 Files selected for processing (2)
  • src/runtime/webcore/Blob.zig
  • test/js/bun/util/filesink.test.ts

Comment on lines +2982 to +2984
if (stream_start == .FileSink) {
stream_start.FileSink.input_path = input_path;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Release previous input_path before overwriting .FileSink.input_path

If options include a valid path, fromJSWithTag allocates it; this assignment overwrites that value without deinit, leaking that allocation.

Proposed fix
         if (stream_start == .FileSink) {
+            stream_start.FileSink.input_path.deinit();
             stream_start.FileSink.input_path = input_path;
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (stream_start == .FileSink) {
stream_start.FileSink.input_path = input_path;
}
if (stream_start == .FileSink) {
stream_start.FileSink.input_path.deinit();
stream_start.FileSink.input_path = input_path;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/runtime/webcore/Blob.zig` around lines 2982 - 2984, The assignment to
stream_start.FileSink.input_path in fromJSWithTag overwrites a previously
allocated value and leaks; before setting stream_start.FileSink.input_path =
input_path, check if stream_start == .FileSink and if the existing
stream_start.FileSink.input_path is non-null/initialized, release it using the
appropriate deinit/free for that type (e.g., call the allocator free or the
value's deinit method), then assign the new input_path; ensure you use the same
allocator/cleanup method used when the path was allocated to avoid double-free
issues.

}

it.skipIf(isWindows)("writer() with invalid path/fd options throws instead of crashing", () => {
const file = path.join(tmpdirSync(), "test.txt");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Use tempDir fixture instead of tmpdirSync() in this new test

Please switch this new temp-path setup to tempDir from harness for consistent cleanup semantics in test files.

Proposed refactor
-  const file = path.join(tmpdirSync(), "test.txt");
+  using dir = tempDir("filesink-invalid-options");
+  const file = path.join(dir, "test.txt");

Also update imports:

-import { fileDescriptorLeakChecker, isPosix, isWindows, tmpdirSync } from "harness";
+import { fileDescriptorLeakChecker, isPosix, isWindows, tempDir, tmpdirSync } from "harness";

As per coding guidelines test/**/*.test.ts: "Use tempDir from harness to create temporary directories; do not use tmpdirSync or fs.mkdtempSync."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const file = path.join(tmpdirSync(), "test.txt");
using dir = tempDir("filesink-invalid-options");
const file = path.join(dir, "test.txt");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/js/bun/util/filesink.test.ts` at line 223, Replace the use of
tmpdirSync() when constructing the test path with the test harness tempDir
fixture: import tempDir from the test harness at top of the file, call tempDir()
within the test to get a temporary directory, and use path.join(tempDir(),
"test.txt") in place of path.join(tmpdirSync(), "test.txt"); ensure any existing
tmpdirSync or fs.mkdtempSync import/usages are removed so cleanup is handled by
the harness.

@github-actions

github-actions Bot commented May 4, 2026

Copy link
Copy Markdown
Contributor

This PR may be a duplicate of:

  1. Add regression test for Bun.file().writer() with invalid path/fd in options #30246 - Same fix: checks .FileSink variant before accessing union field in getWriter for invalid path/fd options
  2. Fix crash in Bun.file().writer() with non-string path option #30232 - Same fix: guards stream_start.FileSink.input_path access behind variant check for non-string path option
  3. Fix crash in Blob.writer() when options parsing returns an error #28736 - Same fix: switches on fromJSWithTag result to handle .err variant in Blob.writer()
  4. Fix crash in Blob.getWriter when options parsing returns an error #28700 - Same fix: checks active union variant before accessing .FileSink, throws JS error for .err
  5. Handle Start::Err variant in Blob.get_writer and FileSink.start #28497 - Same fix: handles error variant from fromJSWithTag in Blob.getWriter
  6. Fix crash in Blob.writer() when options contain invalid fd or path #28388 - Same fix: adds .err variant check before .FileSink access in Blob.writer()

🤖 Generated with Claude Code

@robobun

robobun commented May 4, 2026

Copy link
Copy Markdown
Collaborator Author

Duplicate of #28388 (and #28497, #28700, #28736, #30232, #30246).

@robobun robobun closed this May 4, 2026
@robobun
robobun deleted the farm/0b1bd2d8/blob-writer-filesink-err branch May 4, 2026 19:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant