Skip to content

Validate snapshot offsets before reading the compiled code (#5301) - #5311

Open
1820893135-pixel wants to merge 1 commit into
jerryscript-project:masterfrom
1820893135-pixel:fix-snapshot-offset-validation
Open

1820893135-pixel wants to merge 1 commit into
jerryscript-project:masterfrom
1820893135-pixel:fix-snapshot-offset-validation

Conversation

@1820893135-pixel

Copy link
Copy Markdown

jerry_exec_snapshot() deserializes an external buffer as a snapshot and trusts
the offsets stored in it. Three of them bound the memory it touches, and none
were checked against the buffer size.

1. The function offset (jerry-snapshot.c:915). func_offsets[func_index]
is used to locate the compiled code, then status_flags is read from it:

uint32_t func_offset = header_p->func_offsets[func_index];
ecma_compiled_code_t *bytecode_p = (ecma_compiled_code_t *) (snapshot_data_p + func_offset);

if (bytecode_p->status_flags & CBC_CODE_FLAGS_STATIC_FUNCTION)

2. The declared block size. snapshot_load_compiled_code() takes
bytecode_p->size as the size of the block and everything below - the
argument-header writes, the literal loops, the code_size byte copy - assumes
the block lies inside the input.

3. The literal offsets. For const_literal_end <= i < literal_end,
literal_start_p[i] is used as an offset into the snapshot and recursed into.

A 73-byte malformed snapshot walks off the buffer:

==ERROR: AddressSanitizer: SEGV
    #0 jerry_exec_snapshot jerry-snapshot.c:915

==ERROR: AddressSanitizer: SEGV
    #0 ecma_snapshot_get_literal ecma-literal-storage.c:694

Fix

Check each offset against the end of the snapshot before it is used:

  • func_offset and the literal offsets must leave at least the fixed
    ecma_compiled_code_t header inside the buffer;
  • the declared block size must not extend past the end of the snapshot;
  • the buffer bounds are threaded into snapshot_load_compiled_code() so the
    recursive literal load is checked the same way.

Well-formed snapshots are unaffected (the checks only reject blocks that claim
to extend past the input). Malformed input now returns a normal exception.

Testing

Adds a regression case to tests/unit-core/test-snapshot.c using the malformed
snapshot from the report. With the checks reverted the test aborts with the SEGV
above; with them it passes.

Fixes #5301.

jerry_exec_snapshot() trusted the offsets stored in the snapshot header
and in the bytecode it deserializes:

  * func_offsets[func_index] was used directly to locate the compiled
    code, and its status_flags field was read without checking that the
    offset was inside the snapshot;
  * the size field of the compiled code header was used as the block
    size, so a block could claim to extend beyond the input buffer and
    the argument-header writes and literal loops below would touch
    memory outside it;
  * the literal offsets in the literal table were used the same way when
    recursively loading a nested function.

A short malformed snapshot (73 bytes in the report) therefore made the
engine read and write outside the buffer:

    ==ERROR: AddressSanitizer: SEGV
    #0 jerry_exec_snapshot jerry-snapshot.c:915

Check the fixed header size, the declared block size and the literal
offsets against the end of the snapshot before any of them is used, and
propagate the buffer bounds into snapshot_load_compiled_code() for the
recursive case.  Malformed input now returns a normal exception instead
of dereferencing out-of-bounds memory.

Adds a regression case to tests/unit-core/test-snapshot.c.

JerryScript-DCO-1.0-Signed-off-by: 1820893135-pixel <1820893135@qq.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

1 participant