Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/runtime/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,21 @@ pub(crate) fn with_text_format_source<R>(
_str_hold.slice()
};

// Every parser reached from here records source positions as an `i32`
// (`ast::Loc` via `usize2loc` for JSONC/TOML, JSON5's token locs, YAML's
// `Pos`), so an input those offsets cannot represent panics inside the
// lexer instead of reporting an error. Reject it before parsing.
if bytes.len() > i32::MAX as usize {
return Err(global.throw_range_error(
bytes.len() as i64,
bun_jsc::RangeErrorOptions {
field_name: b"input.byteLength",
max: i64::from(i32::MAX),
..Default::default()
},
));
}

let mut log = bun_ast::Log::init();
let source = bun_ast::Source::init_path_string(path, bytes);

Expand Down
21 changes: 21 additions & 0 deletions test/js/bun/json5/json5.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1699,3 +1699,24 @@
expect({ stdout, stderr, exitCode }).toEqual({ stdout: "", stderr: "", exitCode: 0 });
});
});

// The JSON5 lexer records every source position as an i32, so an input of
// 2**31 bytes or more used to abort the process with
// `panic: int cast: TryFromIntError(PosOverflow)` instead of throwing. It is
// rejected before parsing, so the Uint8Array below is virtual pages that are
// never read. The runtime accepts a TypedArray here (the binding takes a
// Blob, Buffer or string); the declared `string` type is narrower.
test("parse rejects an input of 2**31 bytes or more instead of panicking", () => {
const input = new Uint8Array(2 ** 31 + 2) as unknown as string;

Check warning on line 1710 in test/js/bun/json5/json5.test.ts

View check run for this annotation

Claude / Claude Code Review

Unguarded 2 GiB Uint8Array allocation may fail on Windows/ASAN CI

nit: `new Uint8Array(2 ** 31 + 2)` runs in-process and outside any guard, so if the allocation throws (Windows commit-charge pressure / ASAN), the test goes red instead of skipping. Every other ~2 GiB allocation in this test suite (`web-crypto.test.ts`, `structured-clone.test.ts`, `test-crypto-certificate.js`) wraps the allocation in `try { … } catch { /* skip */ return; }` for exactly this reason — consider doing the same here and at `test/js/bun/yaml/yaml.test.ts:4754`.
Comment thread
robobun marked this conversation as resolved.
Outdated
let err: any;
try {
JSON5.parse(input);
} catch (e) {
err = e;
}
expect(err?.constructor?.name).toBe("RangeError");
expect(err?.code).toBe("ERR_OUT_OF_RANGE");
expect(err?.message).toBe(
'The value of "input.byteLength" is out of range. It must be <= 2147483647. Received 2147483650',
);
});
21 changes: 21 additions & 0 deletions test/js/bun/yaml/yaml.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4743,3 +4743,24 @@ describe("plain scalar whitespace handling", () => {
expect(() => YAML.parse("g: -")).toThrow(SyntaxError);
});
});

// The YAML scanner records every source position as an i32, so an input of
// 2**31 bytes or more used to abort the process with
// `panic: int cast: TryFromIntError(PosOverflow)` instead of throwing. It is
// rejected before parsing, so the Uint8Array below is virtual pages that are
// never read. The runtime accepts a TypedArray here (the binding takes a
// Blob, Buffer or string); the declared `string` type is narrower.
test("parse rejects an input of 2**31 bytes or more instead of panicking", () => {
const input = new Uint8Array(2 ** 31 + 2) as unknown as string;
let err: any;
try {
YAML.parse(input);
} catch (e) {
err = e;
}
expect(err?.constructor?.name).toBe("RangeError");
expect(err?.code).toBe("ERR_OUT_OF_RANGE");
expect(err?.message).toBe(
'The value of "input.byteLength" is out of range. It must be <= 2147483647. Received 2147483650',
);
});
Loading