Bun.mmap: throw RangeError for files > 4 GiB instead of aborting - #34119
Bun.mmap: throw RangeError for files > 4 GiB instead of aborting#34119robobun wants to merge 3 commits into
Conversation
…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.
|
Updated 1:27 AM PT - Jul 14th, 2026
❌ @robobun, your commit b3282fc has 2 failures in
🧪 To try this PR locally: bunx bun-pr 34119That installs a local version of the PR into your bun-34119 --bun |
|
Warning Review limit reached
Next review available in: 4 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the 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 configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (1)
WalkthroughChangesmmap safety
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (2)
src/runtime/api/BunObject.rstest/js/bun/util/mmap.test.js
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.
There was a problem hiding this comment.
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'sm_sizeInBytes <= (1ull << 32)assert — exactly 4 GiB still succeeds, and the test'sat-limitcase covers it. - Error path munmaps before throwing;
let _ =matches the existingmunmap_deallocconvention 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.
|
CI status on b3282fc (build #72722, finished: 284 passed, 2 failed):
Ready for review. |
Reproduction
Release build: silent
SIGABRT, exit 134, empty stderr. Debug build:The boundary is exact:
2 ** 32maps fine,2 ** 32 + 1kills 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_fileinsrc/runtime/api/BunObject.rspasses thefstat()'d size unchecked intomake_typed_array_with_bytes_no_copy. JSC ArrayBuffers are hard-capped atMAX_ARRAY_BUFFER_SIZE(1ull << 32,JavaScriptCore/runtime/PageCount.h), enforced byRELEASE_ASSERTin theArrayBufferContentsconstructor.Fix
After
bun_sys::mmap_filereturns, checkmap.len()against the limit. On overflow,munmapthe region and throw aRangeErrorthat points the user at the existing{ size }option:The check is placed after the mmap (rather than before) because
bun_sys::mmap_filedoes thefstatinternally; checking the returnedmap.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'stoArrayBuffer/toBuffer, a different entry point to the same JSC assertion.Verification
New test in
test/js/bun/util/mmap.test.jscreates a sparse2 ** 32 + 1byte file and spawns a child that:Bun.mmap(f)with no options: must throwRangeErrormentioning4294967297Bun.mmap(f, { size: 2 ** 32 }): exactly at the limit, must succeed with.length === 4294967296Bun.mmap(f, { size: 4096 }): capped, must succeedFails on the released binary with
signalCode: "SIGABRT"/exitCode: 134: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