Skip to content

Bun.mmap: throw RangeError for files > 4 GiB instead of aborting - #34119

Open
robobun wants to merge 3 commits into
mainfrom
farm/f596f2c2/mmap-4gib-range-error
Open

Bun.mmap: throw RangeError for files > 4 GiB instead of aborting#34119
robobun wants to merge 3 commits into
mainfrom
farm/f596f2c2/mmap-4gib-range-error

Conversation

@robobun

@robobun robobun commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

Reproduction

import { writeFileSync, truncateSync } from "node:fs";
writeFileSync("/tmp/big.bin", "");
truncateSync("/tmp/big.bin", 2 ** 32 + 1);   // sparse, no disk used
Bun.mmap("/tmp/big.bin");

Release build: silent SIGABRT, exit 134, empty stderr. Debug build:

ASSERTION FAILED: m_sizeInBytes <= (1ull << 32)
vendor/WebKit/Source/JavaScriptCore/runtime/ArrayBuffer.cpp:150 (ArrayBufferContents ctor)

The boundary is exact: 2 ** 32 maps fine, 2 ** 32 + 1 kills the process. mmap is the API you reach for on huge files, so the first >4 GiB log, VM image, or dataset takes the whole process down with no catchable error.

Cause

mmap_file in src/runtime/api/BunObject.rs passes the fstat()'d size unchecked into make_typed_array_with_bytes_no_copy. JSC ArrayBuffers are hard-capped at MAX_ARRAY_BUFFER_SIZE (1ull << 32, JavaScriptCore/runtime/PageCount.h), enforced by RELEASE_ASSERT in the ArrayBufferContents constructor.

Fix

After bun_sys::mmap_file returns, check map.len() against the limit. On overflow, munmap the region and throw a RangeError that points the user at the existing { size } option:

RangeError: File is too large to mmap: 4294967297 bytes exceeds the maximum
typed array size (4294967296 bytes). Pass { size } to map a smaller range.

The check is placed after the mmap (rather than before) because bun_sys::mmap_file does the fstat internally; checking the returned map.len() also covers an explicit { size } that exceeds the cap after being clamped to the file length.

Related: #33353 applies the same bound to bun:ffi's toArrayBuffer/toBuffer, a different entry point to the same JSC assertion.

Verification

New test in test/js/bun/util/mmap.test.js creates a sparse 2 ** 32 + 1 byte file and spawns a child that:

  • Bun.mmap(f) with no options: must throw RangeError mentioning 4294967297
  • Bun.mmap(f, { size: 2 ** 32 }): exactly at the limit, must succeed with .length === 4294967296
  • Bun.mmap(f, { size: 4096 }): capped, must succeed

Fails on the released binary with signalCode: "SIGABRT" / exitCode: 134:

USE_SYSTEM_BUN=1 bun test test/js/bun/util/mmap.test.js -t "4 GiB"
  {
-   exitCode: 0,          +   exitCode: 134,
-   signalCode: null,     +   signalCode: "SIGABRT",
    ...
  }

Passes on the debug build (all 9 mmap tests green).


no test proof · iteration 0 · Platform-specific test(s) that do not run on this machine. Deferring to CI, which covers all platforms: test/js/bun/util/mmap.test.js

…rting

JSC ArrayBuffers are hard-capped at MAX_ARRAY_BUFFER_SIZE (1<<32 bytes,
JavaScriptCore/runtime/PageCount.h), enforced by RELEASE_ASSERT in the
ArrayBufferContents constructor. mmap_file passed the fstat()'d size
straight into make_typed_array_with_bytes_no_copy with no bound check,
so mapping any file one byte past 4 GiB aborted the whole process with
no catchable error.

Check map.len() against the limit after the mmap succeeds; on overflow,
munmap and throw a RangeError that points at the {size} option.
@robobun

robobun commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 1:27 AM PT - Jul 14th, 2026

@robobun, your commit b3282fc has 2 failures in Build #72722 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 34119

That installs a local version of the PR into your bun-34119 executable, so you can run:

bun-34119 --bun

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@robobun, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 4 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: cc6424a9-7609-4cfb-ae57-900a8bda3b9d

📥 Commits

Reviewing files that changed from the base of the PR and between 3d4ce5f and b3282fc.

📒 Files selected for processing (1)
  • test/js/bun/util/mmap.test.js

Walkthrough

Changes

mmap safety

Layer / File(s) Summary
Oversized mapping guard
src/runtime/api/BunObject.rs
mmap_file caps mappings at MAX_ARRAY_BUFFER_SIZE, unmaps oversized regions, and returns a descriptive RangeError.
Large-file regression coverage
test/js/bun/util/mmap.test.js
A spawned Bun process verifies oversized-file errors, 4 GiB boundary support, smaller explicit mappings, and cleanup behavior.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: Bun.mmap now throws RangeError instead of aborting for >4 GiB files.
Description check ✅ Passed The description covers the change and verification thoroughly, though it uses custom headings instead of the template's exact section titles.
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.

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

@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/api/BunObject.rs`:
- Around line 1844-1852: Update the oversized-mapping cleanup in the error path
of the surrounding mmap function to handle the result of sys::munmap instead of
discarding it. Preserve the RangeError return, but log any unmap failure with
scoped_log! and include the failure details for diagnostics.

In `@test/js/bun/util/mmap.test.js`:
- Around line 89-125: Remove the generic stderr content assertion from the mmap
subprocess test after the existing stdout, signalCode, and exitCode
expectations. Keep awaiting proc.stderr.text() so stderr remains drained, and
rely on the process outcome fields for abort detection.
🪄 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: f1caaf99-c3da-498d-843a-cb4f6c6e0420

📥 Commits

Reviewing files that changed from the base of the PR and between 16c5576 and 2944959.

📒 Files selected for processing (2)
  • src/runtime/api/BunObject.rs
  • test/js/bun/util/mmap.test.js

Comment thread src/runtime/api/BunObject.rs
Comment thread test/js/bun/util/mmap.test.js Outdated
signalCode: null and exitCode: 0 in the toEqual already detect any abort;
the stderr check is redundant and can false-positive on benign debug output.
Comment thread test/js/bun/util/mmap.test.js Outdated

@claude claude 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.

LGTM — the stderr-marker nit from my earlier pass is addressed in b3282fc, and the remaining change is a straightforward bounds check that turns a JSC RELEASE_ASSERT abort into a catchable RangeError.

What was reviewed:

  • Boundary is > (1 << 32), matching JSC's m_sizeInBytes <= (1ull << 32) assert — exactly 4 GiB still succeeds, and the test's at-limit case covers it.
  • Error path munmaps before throwing; let _ = matches the existing munmap_dealloc convention directly below.
  • Test spawns a subprocess so a SIGABRT regression fails the assertion (signalCode: null / exitCode: 0) rather than the runner; stderr is drained concurrently but not asserted on.
Extended reasoning...

Overview

Two files: a 13-line addition to mmap_file in src/runtime/api/BunObject.rs that checks map.len() against JSC's MAX_ARRAY_BUFFER_SIZE (1 << 32) after the mmap succeeds, munmaps and throws a RangeError on overflow; and a new subprocess test in test/js/bun/util/mmap.test.js covering >4 GiB (throws), exactly 4 GiB (succeeds), and a capped { size: 4096 } (succeeds).

Security risks

None. This adds a defensive size check to a local-file mmap path that previously aborted the process. It strictly narrows the reachable behavior (abort → catchable error) and does not expose any new surface, change permissions, or handle untrusted network input.

Level of scrutiny

Low-to-moderate. The Rust change is a single guarded early-return with cleanup, following the exact pattern already used a few lines below (munmap_dealloc also discards the munmap result with let _ =). I verified: the constant matches JSC's MAX_ARRAY_BUFFER_SIZE (also referenced in src/jsc/bindings/JSBuffer.{h,cpp}); the comparison operator (>) matches the JSC assert's inclusive bound (<=); sys::munmap's signature (*mut u8, usize) matches the call; and sys / create_range_error_instance are already in scope. On 64-bit targets (all Bun targets) 1usize << 32 is well-defined.

Other factors

All prior review threads are resolved: my earlier nit about the .not.toContain("ASSERTION FAILED") assertion was addressed in b3282fc (stderr is now drained via Promise.all but not asserted on), and the CodeRabbit munmap-logging suggestion was withdrawn after the author pointed out it matches the existing munmap_dealloc convention and cannot realistically fail on a just-returned mapping. The test uses a sparse file (no disk cost), spawns a child so a regressed SIGABRT surfaces as signalCode: "SIGABRT" in the toEqual rather than killing the runner, and matches the file's existing tmpdirSync/bunExe/bunEnv conventions. The bug hunting system found no issues.

@robobun

robobun commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator Author

CI status on b3282fc (build #72722, finished: 284 passed, 2 failed):

  • test/js/bun/util/mmap.test.js passed on every lane.
  • cargo clippy (GitHub Actions) is red on src/runtime/napi/napi_body.rs:2811 (undocumented_unsafe_blocks), introduced on main by 73b6c14 (napi: keep threadsafe functions alive after their env is torn down #34067). Failing across all branches; this diff doesn't touch that file.
  • test/cli/run/no-orphans.test.ts timed out after 30s on darwin 26 aarch64 (bun run --no-orphans (perl): fast-exit intermediate ... daemon still reaped). Process-reaping test, unrelated to Bun.mmap.
  • test/js/third_party/grpc-js/test-tonic.test.ts failed on darwin 14 aarch64 with rustup could not choose a version of cargo to run, because one wasn't specified explicitly, and no default is configured. CI machine configuration issue.
  • All other annotations are [flaky] (passed on retry).

Ready for review.

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