From a0c5f3dc69418d28c7f37a1de6f554e3825d5e7b Mon Sep 17 00:00:00 2001 From: robobun Date: Wed, 26 Nov 2025 13:37:41 -0800 Subject: [PATCH] fix(mmap): use coerceToInt64 for offset/size to prevent assertion failure (#25101) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Fix assertion failure in `Bun.mmap` when `offset` or `size` options are non-numeric values - Add validation to reject negative `offset`/`size` with clear error messages Minimal reproduction: `Bun.mmap("", { offset: null });` ## Root Cause `Bun.mmap` was calling `toInt64()` directly on the `offset` and `size` options without validating they are numbers first. `toInt64()` has an assertion that the value must be a number or BigInt, which fails when non-numeric values like `null` or functions are passed. ## Test plan - [x] Added tests for negative offset/size rejection - [x] Added tests for non-number inputs (null, undefined) - [x] `bun bd test test/js/bun/util/mmap.test.js` passes Closes ENG-22413 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Bot Co-authored-by: Claude --- src/bun.js/api/BunObject.zig | 12 ++++++++++-- test/js/bun/util/mmap.test.js | 28 ++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/bun.js/api/BunObject.zig b/src/bun.js/api/BunObject.zig index 1b28383086..48f090bb7a 100644 --- a/src/bun.js/api/BunObject.zig +++ b/src/bun.js/api/BunObject.zig @@ -1212,11 +1212,19 @@ pub fn mmapFile(globalThis: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) bun. } if (try opts.get(globalThis, "size")) |value| { - map_size = @as(usize, @intCast(value.toInt64())); + const size_value = try value.coerceToInt64(globalThis); + if (size_value < 0) { + return globalThis.throwInvalidArguments("size must be a non-negative integer", .{}); + } + map_size = @intCast(size_value); } if (try opts.get(globalThis, "offset")) |value| { - offset = @as(usize, @intCast(value.toInt64())); + const offset_value = try value.coerceToInt64(globalThis); + if (offset_value < 0) { + return globalThis.throwInvalidArguments("offset must be a non-negative integer", .{}); + } + offset = @intCast(offset_value); offset = std.mem.alignBackwardAnyAlign(usize, offset, std.heap.pageSize()); } } diff --git a/test/js/bun/util/mmap.test.js b/test/js/bun/util/mmap.test.js index 57a3f43e64..768550c855 100644 --- a/test/js/bun/util/mmap.test.js +++ b/test/js/bun/util/mmap.test.js @@ -68,4 +68,32 @@ describe.skipIf(isWindows)("Bun.mmap", async () => { expect(map[0]).toBe(old); await gcTick(); }); + + it("mmap rejects negative offset", () => { + expect(() => Bun.mmap(path, { offset: -1 })).toThrow("offset must be a non-negative integer"); + }); + + it("mmap rejects negative size", () => { + expect(() => Bun.mmap(path, { size: -1 })).toThrow("size must be a non-negative integer"); + }); + + it("mmap handles non-number offset/size without crashing", () => { + // These should not crash - non-number values coerce to 0 per JavaScript semantics + // Previously these caused assertion failures (issue ENG-22413) + + // null coerces to 0, which is valid for offset + expect(() => { + Bun.mmap(path, { offset: null }); + }).not.toThrow(); + + // size: null coerces to 0, which is invalid (EINVAL), but shouldn't crash + expect(() => { + Bun.mmap(path, { size: null }); + }).toThrow("EINVAL"); + + // undefined is ignored (property not set) + expect(() => { + Bun.mmap(path, { offset: undefined }); + }).not.toThrow(); + }); });