Skip to content

Commit 42a8a03

Browse files
committed
FIX: prevent large JS integer wraparound
Convert integral JavaScript numbers to Ruby integers without truncating through Fixnum-sized conversions, preserving values at and above 2^62. Add coverage for large numeric eval results and callback arguments, and update the changelog.
1 parent 5c38c70 commit 42a8a03

3 files changed

Lines changed: 30 additions & 4 deletions

File tree

CHANGELOG

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
- (Unreleased)
22
- Add `Context#perform_microtask_checkpoint` to synchronously drain the V8 microtask queue, useful for spec-compliant `dispatchEvent` sequencing inside Ruby callbacks
3-
- Fix native memory leaks in `Context#heap_snapshot`/`Context#write_heap_snapshot`; thanks to Pranjali Thakur from depthfrist.com
3+
- Fix native memory leaks in `Context#heap_snapshot`/`Context#write_heap_snapshot`; thanks to Pranjali Thakur from depthfirst.com
4+
- Fix large integral JavaScript numbers wrapping to negative Ruby integers; thanks to Pranjali Thakur from depthfirst.com
45

56
- 0.21.1 - 25-05-2026
67
- Run `:single_threaded` V8 dispatches on a reusable mini_racer-owned native thread so V8 does not execute on Ruby-owned threads

ext/mini_racer_extension/mini_racer_extension.c

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -316,13 +316,19 @@ static void des_bool(void *arg, int v)
316316

317317
static void des_int(void *arg, int64_t v)
318318
{
319-
put(arg, LONG2FIX(v));
319+
put(arg, LL2NUM((LONG_LONG)v));
320320
}
321321

322322
static void des_num(void *arg, double v)
323323
{
324-
if (isfinite(v) && v == trunc(v) && v >= INT64_MIN && v <= INT64_MAX) {
325-
put(arg, LONG2FIX(v));
324+
if (isfinite(v) && v == trunc(v)) {
325+
// INT64_MAX is not exactly representable as a double: it rounds up to
326+
// 2^63, which would let 2^63 through and make the cast undefined.
327+
if (v >= -0x1p63 && v < 0x1p63) {
328+
put(arg, LL2NUM((LONG_LONG)v));
329+
} else {
330+
put(arg, rb_dbl2big(v));
331+
}
326332
} else {
327333
put(arg, DBL2NUM(v));
328334
}

test/mini_racer_test.rb

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1228,6 +1228,25 @@ def test_ruby_exception
12281228
end
12291229
end
12301230

1231+
def test_large_javascript_numbers_do_not_wrap
1232+
context = MiniRacer::Context.new
1233+
1234+
{
1235+
"Number.MAX_SAFE_INTEGER" => 2**53 - 1,
1236+
"2**62" => 2**62,
1237+
"2**63" => 2**63,
1238+
"-(2**62) - 1024" => -(2**62) - 1024,
1239+
}.each do |source, expected|
1240+
result = context.eval(source)
1241+
assert_kind_of Integer, result
1242+
assert_equal expected, result
1243+
end
1244+
1245+
context.attach("allow_admin", proc { |uid| [uid, uid < 0] })
1246+
assert_equal [7, false], context.eval("allow_admin(7)")
1247+
assert_equal [2**62, false], context.eval("allow_admin(2**62)")
1248+
end
1249+
12311250
def test_large_integer
12321251
[10_000_000_001, -2**63, 2**63-1].each { |big_int|
12331252
context = MiniRacer::Context.new

0 commit comments

Comments
 (0)