Skip to content

Commit 4b00d36

Browse files
Validate snapshot offsets before reading the compiled code
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>
1 parent b706935 commit 4b00d36

2 files changed

Lines changed: 98 additions & 3 deletions

File tree

‎jerry-core/api/jerry-snapshot.c‎

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -542,11 +542,32 @@ snapshot_load_compiled_code (const uint8_t *base_addr_p, /**< base address of th
542542
* current primary function */
543543
const uint8_t *literal_base_p, /**< literal start */
544544
cbc_script_t *script_p, /**< script */
545-
bool copy_bytecode) /**< byte code should be copied to memory */
545+
bool copy_bytecode, /**< byte code should be copied to memory */
546+
size_t snapshot_size, /**< total size of the snapshot buffer */
547+
const uint8_t *snapshot_end_p) /**< end of the snapshot buffer */
546548
{
549+
/* base_addr_p is derived from an offset stored in the snapshot, so it may point
550+
* anywhere up to the end of the buffer. Do not dereference it before making sure
551+
* the fixed part of the compiled code header is actually there. */
552+
if (base_addr_p > snapshot_end_p
553+
|| (size_t) (snapshot_end_p - base_addr_p) < sizeof (ecma_compiled_code_t))
554+
{
555+
return NULL;
556+
}
557+
547558
ecma_compiled_code_t *bytecode_p = (ecma_compiled_code_t *) base_addr_p;
548559
uint32_t code_size = ((uint32_t) bytecode_p->size) << JMEM_ALIGNMENT_LOG;
549560

561+
/* The size stored in the compiled code header is only as trustworthy as the rest
562+
* of the snapshot. Everything below (the arguments header fields, the literal
563+
* loops and the code_size byte copy) assumes the whole block lies inside the input
564+
* buffer, so check that before reading any further. */
565+
if (code_size < sizeof (cbc_uint8_arguments_t)
566+
|| code_size > (size_t) (snapshot_end_p - base_addr_p))
567+
{
568+
return NULL;
569+
}
570+
550571
#if JERRY_BUILTIN_REGEXP
551572
if (!CBC_IS_FUNCTION (bytecode_p->status_flags))
552573
{
@@ -698,8 +719,22 @@ snapshot_load_compiled_code (const uint8_t *base_addr_p, /**< base address of th
698719
else
699720
{
700721
ecma_compiled_code_t *literal_bytecode_p;
722+
723+
/* literal_offset is another unchecked offset from the snapshot. */
724+
if (literal_offset > snapshot_size
725+
|| snapshot_size - literal_offset < sizeof (ecma_compiled_code_t))
726+
{
727+
return NULL;
728+
}
729+
701730
literal_bytecode_p =
702-
snapshot_load_compiled_code (base_addr_p + literal_offset, literal_base_p, script_p, copy_bytecode);
731+
snapshot_load_compiled_code (base_addr_p + literal_offset, literal_base_p, script_p, copy_bytecode,
732+
snapshot_size, snapshot_end_p);
733+
734+
if (literal_bytecode_p == NULL)
735+
{
736+
return NULL;
737+
}
703738

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

912947
uint32_t func_offset = header_p->func_offsets[func_index];
948+
949+
/* func_offsets comes straight from the snapshot header and is never checked, so
950+
* the compiled code it points at may be outside the buffer. Verify it is inside
951+
* before reading status_flags, otherwise a handful of crafted bytes walk off the
952+
* end of the snapshot. */
953+
if (func_offset > snapshot_size || snapshot_size - func_offset < sizeof (ecma_compiled_code_t))
954+
{
955+
return jerry_throw_sz (JERRY_ERROR_TYPE, ecma_get_error_msg (ECMA_ERR_INVALID_SNAPSHOT_FORMAT));
956+
}
957+
913958
ecma_compiled_code_t *bytecode_p = (ecma_compiled_code_t *) (snapshot_data_p + func_offset);
914959

915960
if (bytecode_p->status_flags & CBC_CODE_FLAGS_STATIC_FUNCTION)
@@ -971,7 +1016,9 @@ jerry_exec_snapshot (const uint32_t *snapshot_p, /**< snapshot */
9711016
bytecode_p = snapshot_load_compiled_code ((const uint8_t *) bytecode_p,
9721017
literal_base_p,
9731018
script_p,
974-
(exec_snapshot_opts & JERRY_SNAPSHOT_EXEC_COPY_DATA) != 0);
1019+
(exec_snapshot_opts & JERRY_SNAPSHOT_EXEC_COPY_DATA) != 0,
1020+
snapshot_size,
1021+
snapshot_data_p + snapshot_size);
9751022

9761023
if (bytecode_p == NULL)
9771024
{

‎tests/unit-core/test-snapshot.c‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,18 @@
2323
*/
2424
#define SNAPSHOT_BUFFER_SIZE (256)
2525

26+
/* malformed_snapshot_literal_offset: 73 bytes */
27+
static const uint8_t malformed_snapshot_literal_offset[] = {
28+
0x53, 0x4e, 0x41, 0x50, 0x4a, 0x52, 0x52, 0x59, 0x46, 0x00, 0x00, 0x00,
29+
0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
30+
0xf0, 0xff, 0xff, 0xff, 0x05, 0x00, 0x01, 0x00, 0x00, 0x30, 0x01, 0x00,
31+
0x26, 0x00, 0x00, 0x00, 0x01, 0x02, 0x02, 0x02, 0x07, 0x00, 0x00, 0x00,
32+
0x47, 0x01, 0x51, 0x01, 0x35, 0x00, 0xdf, 0x01, 0x56, 0x00, 0x00, 0x00,
33+
0x00, 0x00, 0x00, 0x00, 0x87, 0x00, 0x00, 0x00, 0x01, 0x00, 0x61, 0x00,
34+
0x00,
35+
};
36+
37+
2638
/**
2739
* Maximum size of literal buffer
2840
*/
@@ -436,11 +448,47 @@ test_snapshot_with_user (void)
436448
}
437449
} /* test_snapshot_with_user */
438450

451+
/*
452+
* Executing a snapshot whose offsets are not validated must fail cleanly instead
453+
* of reading outside the input buffer. The two inputs below come from two
454+
* defect: the compiled-code offset stored in the snapshot header is used without
455+
* being checked against the buffer size.
456+
*/
457+
static void
458+
test_malformed_snapshot (void)
459+
{
460+
if (!jerry_feature_enabled (JERRY_FEATURE_SNAPSHOT_EXEC))
461+
{
462+
return;
463+
}
464+
465+
/* jerry_exec_snapshot() patches the snapshot in place, so hand it a writable
466+
* copy of the input rather than the read-only array above. */
467+
uint8_t snapshot_buffer[sizeof (malformed_snapshot_literal_offset)];
468+
jerry_value_t result;
469+
470+
jerry_init (JERRY_INIT_EMPTY);
471+
472+
memcpy (snapshot_buffer, malformed_snapshot_literal_offset, sizeof (malformed_snapshot_literal_offset));
473+
result = jerry_exec_snapshot ((const uint32_t *) (snapshot_buffer + 4),
474+
sizeof (malformed_snapshot_literal_offset) - 4,
475+
0,
476+
JERRY_SNAPSHOT_EXEC_COPY_DATA,
477+
NULL);
478+
TEST_ASSERT (jerry_value_is_exception (result));
479+
jerry_value_free (result);
480+
481+
482+
jerry_cleanup ();
483+
} /* test_malformed_snapshot */
484+
439485
int
440486
main (void)
441487
{
442488
TEST_INIT ();
443489

490+
test_malformed_snapshot ();
491+
444492
test_static_snapshot ();
445493

446494
test_merge_snapshot ();

0 commit comments

Comments
 (0)