Summary
lexer_construct_number_object converts a JS numeric literal to an ecma_number and, in expression context, executes int32_t int_num = (int32_t) num; to attempt folding into a CBC_PUSH_NUMBER_BYTE_RANGE immediate. When the literal exceeds the int32 range (e.g., 1e308, or values above 2**53), the double-to-int32 cast is undefined behavior; UBSan trap mode executes __builtin_trap() and raises SIGILL. The trigger is plain, valid JavaScript — no malformed input is required.
- Affected: JerryScript 3.0.0 (
jerry-core/parser/js/js-lexer.c:2539 in lexer_construct_number_object)
- Severity: Medium
- CWE: CWE-681 (Incorrect Conversion between Numeric Types)
POC (plain JS, no malformed input)
// poc.js — valid JavaScript with oversized numeric literals
var a = 0xFF ^ 0x0F; var b = 1 << 20; var c = 3.14159; var d = -1.5e10; a += b; var r = a * c + d; r;
# 1) build (ASan+UBSan with trap)
cmake -S . -B build -DENABLE_LTO=OFF -DJERRY_SNAPSHOT_EXEC=ON -DJERRY_SNAPSHOT_SAVE=ON \
-DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_C_COMPILER=clang \
-DCMAKE_C_FLAGS="-fsanitize=address,undefined -fno-sanitize-recover=all -O1 -g"
cmake --build build --target jerry-core jerry-port -j$(nproc)
clang -fsanitize=fuzzer,address,undefined -fno-sanitize-recover=all -O1 -g -DJERRY_SNAPSHOT_EXEC=1 \
-I jerry-core/include harness.c build/lib/libjerry-core.a build/lib/libjerry-port.a -lm -o jerry_fuzzer
# 2) reproduce
./jerry_fuzzer poc.js
Trigger result
ERROR: UBSan: runtime error: value ... is outside the range of representable values of type 'int' (SIGILL)
#0 ... lexer_construct_number_object js-lexer.c:2539
- Replay exit code:
132 (SIGILL)
- Deterministic: yes — also triggered by the snapshot-deserialization number path (
crash_seed_seed_refine_143_eval_numbers.bin).
Impact
Any application that submits JS source to jerry_parse/jerry_eval containing an oversized numeric literal SIGILL-crashes under a UBSan-trap build (the OSS-Fuzz official configuration) — stable DoS. In a regular build, the hardware cast yields 0x80000000, and the int_num == num check usually makes the fold not apply, so the semantic impact is limited; but the UBSan-trap build (as configured by OSS-Fuzz) is a stable DoS. This particularly affects web/edge runtimes that take untrusted JS as input.
Suggested fix
In lexer_construct_number_object (js-lexer.c:2539), only perform the (int32_t) fold when num is exactly representable as an int32_t (e.g., check num >= -2147483648.0 && num <= 2147483647.0 && (int32_t)num == num), and skip the immediate-fold path otherwise — avoiding the UB cast on oversized literals.
Summary
lexer_construct_number_objectconverts a JS numeric literal to anecma_numberand, in expression context, executesint32_t int_num = (int32_t) num;to attempt folding into aCBC_PUSH_NUMBER_BYTE_RANGEimmediate. When the literal exceeds theint32range (e.g.,1e308, or values above2**53), the double-to-int32 cast is undefined behavior; UBSan trap mode executes__builtin_trap()and raises SIGILL. The trigger is plain, valid JavaScript — no malformed input is required.jerry-core/parser/js/js-lexer.c:2539inlexer_construct_number_object)POC (plain JS, no malformed input)
Trigger result
132(SIGILL)crash_seed_seed_refine_143_eval_numbers.bin).Impact
Any application that submits JS source to
jerry_parse/jerry_evalcontaining an oversized numeric literal SIGILL-crashes under a UBSan-trap build (the OSS-Fuzz official configuration) — stable DoS. In a regular build, the hardware cast yields0x80000000, and theint_num == numcheck usually makes the fold not apply, so the semantic impact is limited; but the UBSan-trap build (as configured by OSS-Fuzz) is a stable DoS. This particularly affects web/edge runtimes that take untrusted JS as input.Suggested fix
In
lexer_construct_number_object(js-lexer.c:2539), only perform the(int32_t)fold whennumis exactly representable as anint32_t(e.g., checknum >= -2147483648.0 && num <= 2147483647.0 && (int32_t)num == num), and skip the immediate-fold path otherwise — avoiding the UB cast on oversized literals.