diff --git a/jerry-core/api/jerry-snapshot.c b/jerry-core/api/jerry-snapshot.c index 33760b299f..e6484c569a 100644 --- a/jerry-core/api/jerry-snapshot.c +++ b/jerry-core/api/jerry-snapshot.c @@ -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)) { @@ -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); } @@ -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) @@ -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) { diff --git a/tests/unit-core/test-snapshot.c b/tests/unit-core/test-snapshot.c index 4de459c1a5..fdc40b05a2 100644 --- a/tests/unit-core/test-snapshot.c +++ b/tests/unit-core/test-snapshot.c @@ -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 */ @@ -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 ();