Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 50 additions & 3 deletions jerry-core/api/jerry-snapshot.c
Original file line number Diff line number Diff line change
Expand Up @@ -542,11 +542,32 @@ snapshot_load_compiled_code (const uint8_t *base_addr_p, /**< base address of th
* current primary function */
const uint8_t *literal_base_p, /**< literal start */
cbc_script_t *script_p, /**< script */
bool copy_bytecode) /**< byte code should be copied to memory */
bool copy_bytecode, /**< byte code should be copied to memory */
size_t snapshot_size, /**< total size of the snapshot buffer */
const uint8_t *snapshot_end_p) /**< end of the snapshot buffer */
{
/* base_addr_p is derived from an offset stored in the snapshot, so it may point
* anywhere up to the end of the buffer. Do not dereference it before making sure
* the fixed part of the compiled code header is actually there. */
if (base_addr_p > snapshot_end_p
|| (size_t) (snapshot_end_p - base_addr_p) < sizeof (ecma_compiled_code_t))
{
return NULL;
}

ecma_compiled_code_t *bytecode_p = (ecma_compiled_code_t *) base_addr_p;
uint32_t code_size = ((uint32_t) bytecode_p->size) << JMEM_ALIGNMENT_LOG;

/* The size stored in the compiled code header is only as trustworthy as the rest
* of the snapshot. Everything below (the arguments header fields, the literal
* loops and the code_size byte copy) assumes the whole block lies inside the input
* buffer, so check that before reading any further. */
if (code_size < sizeof (cbc_uint8_arguments_t)
|| code_size > (size_t) (snapshot_end_p - base_addr_p))
{
return NULL;
}

#if JERRY_BUILTIN_REGEXP
if (!CBC_IS_FUNCTION (bytecode_p->status_flags))
{
Expand Down Expand Up @@ -698,8 +719,22 @@ snapshot_load_compiled_code (const uint8_t *base_addr_p, /**< base address of th
else
{
ecma_compiled_code_t *literal_bytecode_p;

/* literal_offset is another unchecked offset from the snapshot. */
if (literal_offset > snapshot_size
|| snapshot_size - literal_offset < sizeof (ecma_compiled_code_t))
{
return NULL;
}

literal_bytecode_p =
snapshot_load_compiled_code (base_addr_p + literal_offset, literal_base_p, script_p, copy_bytecode);
snapshot_load_compiled_code (base_addr_p + literal_offset, literal_base_p, script_p, copy_bytecode,
snapshot_size, snapshot_end_p);

if (literal_bytecode_p == NULL)
{
return NULL;
}

ECMA_SET_INTERNAL_VALUE_POINTER (literal_start_p[i], literal_bytecode_p);
}
Expand Down Expand Up @@ -910,6 +945,16 @@ jerry_exec_snapshot (const uint32_t *snapshot_p, /**< snapshot */
JERRY_ASSERT ((header_p->lit_table_offset % sizeof (uint32_t)) == 0);

uint32_t func_offset = header_p->func_offsets[func_index];

/* func_offsets comes straight from the snapshot header and is never checked, so
* the compiled code it points at may be outside the buffer. Verify it is inside
* before reading status_flags, otherwise a handful of crafted bytes walk off the
* end of the snapshot. */
if (func_offset > snapshot_size || snapshot_size - func_offset < sizeof (ecma_compiled_code_t))
{
return jerry_throw_sz (JERRY_ERROR_TYPE, ecma_get_error_msg (ECMA_ERR_INVALID_SNAPSHOT_FORMAT));
}

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)
Expand Down Expand Up @@ -971,7 +1016,9 @@ jerry_exec_snapshot (const uint32_t *snapshot_p, /**< snapshot */
bytecode_p = snapshot_load_compiled_code ((const uint8_t *) bytecode_p,
literal_base_p,
script_p,
(exec_snapshot_opts & JERRY_SNAPSHOT_EXEC_COPY_DATA) != 0);
(exec_snapshot_opts & JERRY_SNAPSHOT_EXEC_COPY_DATA) != 0,
snapshot_size,
snapshot_data_p + snapshot_size);

if (bytecode_p == NULL)
{
Expand Down
48 changes: 48 additions & 0 deletions tests/unit-core/test-snapshot.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,18 @@
*/
#define SNAPSHOT_BUFFER_SIZE (256)

/* malformed_snapshot_literal_offset: 73 bytes */
static const uint8_t malformed_snapshot_literal_offset[] = {
0x53, 0x4e, 0x41, 0x50, 0x4a, 0x52, 0x52, 0x59, 0x46, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0xf0, 0xff, 0xff, 0xff, 0x05, 0x00, 0x01, 0x00, 0x00, 0x30, 0x01, 0x00,
0x26, 0x00, 0x00, 0x00, 0x01, 0x02, 0x02, 0x02, 0x07, 0x00, 0x00, 0x00,
0x47, 0x01, 0x51, 0x01, 0x35, 0x00, 0xdf, 0x01, 0x56, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x87, 0x00, 0x00, 0x00, 0x01, 0x00, 0x61, 0x00,
0x00,
};


/**
* Maximum size of literal buffer
*/
Expand Down Expand Up @@ -436,11 +448,47 @@ test_snapshot_with_user (void)
}
} /* test_snapshot_with_user */

/*
* Executing a snapshot whose offsets are not validated must fail cleanly instead
* of reading outside the input buffer. The two inputs below come from two
* defect: the compiled-code offset stored in the snapshot header is used without
* being checked against the buffer size.
*/
static void
test_malformed_snapshot (void)
{
if (!jerry_feature_enabled (JERRY_FEATURE_SNAPSHOT_EXEC))
{
return;
}

/* jerry_exec_snapshot() patches the snapshot in place, so hand it a writable
* copy of the input rather than the read-only array above. */
uint8_t snapshot_buffer[sizeof (malformed_snapshot_literal_offset)];
jerry_value_t result;

jerry_init (JERRY_INIT_EMPTY);

memcpy (snapshot_buffer, malformed_snapshot_literal_offset, sizeof (malformed_snapshot_literal_offset));
result = jerry_exec_snapshot ((const uint32_t *) (snapshot_buffer + 4),
sizeof (malformed_snapshot_literal_offset) - 4,
0,
JERRY_SNAPSHOT_EXEC_COPY_DATA,
NULL);
TEST_ASSERT (jerry_value_is_exception (result));
jerry_value_free (result);


jerry_cleanup ();
} /* test_malformed_snapshot */

int
main (void)
{
TEST_INIT ();

test_malformed_snapshot ();

test_static_snapshot ();

test_merge_snapshot ();
Expand Down