Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
28 changes: 28 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,31 @@ describe("stringify memory", () => {
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", () => {
let input: Uint8Array;
try {
input = new Uint8Array(2 ** 31 + 2);
} catch {
// The 2 GiB reservation itself can fail on a memory-pressured runner;
// there is nothing to test then.
return;
}
let err: any;
try {
JSON5.parse(input as unknown as string);
} 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',
);
});
28 changes: 28 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,31 @@ 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", () => {
let input: Uint8Array;
try {
input = new Uint8Array(2 ** 31 + 2);
} catch {
// The 2 GiB reservation itself can fail on a memory-pressured runner;
// there is nothing to test then.
return;
}
let err: any;
try {
YAML.parse(input as unknown as string);
} 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