diff --git a/reftable/block.c b/reftable/block.c index 89efce8751..1fa81405d2 100644 --- a/reftable/block.c +++ b/reftable/block.c @@ -440,6 +440,15 @@ static int restart_needle_less(size_t idx, void *_args) uint8_t extra; int n; + /* + * The restart offset must point to a record, which is stored before + * the restart table. Verify that this is the case. + */ + if (off >= args->block->restart_off) { + args->error = 1; + return -1; + } + /* * Records at restart points are stored without prefix compression, so * there is no need to fully decode the record key here. This removes diff --git a/t/unit-tests/u-reftable-block.c b/t/unit-tests/u-reftable-block.c index ba410a0885..77a054d082 100644 --- a/t/unit-tests/u-reftable-block.c +++ b/t/unit-tests/u-reftable-block.c @@ -591,3 +591,54 @@ void test_reftable_block__corrupt_restart_count(void) block_writer_release(&writer); reftable_buf_release(&data); } + +void test_reftable_block__corrupt_restart_offset(void) +{ + struct reftable_block_source source = { 0 }; + struct block_writer writer = { + .last_key = REFTABLE_BUF_INIT, + }; + struct reftable_record rec = { + .type = REFTABLE_BLOCK_TYPE_REF, + .u.ref = { + .value_type = REFTABLE_REF_VAL1, + .refname = (char *) "refs/heads/main", + }, + }; + struct reftable_block block = { 0 }; + struct block_iter it = BLOCK_ITER_INIT; + struct reftable_buf want = REFTABLE_BUF_INIT; + struct reftable_buf data; + + data.len = 1024; + REFTABLE_CALLOC_ARRAY(data.buf, data.len); + cl_assert(data.buf != NULL); + + cl_must_pass(block_writer_init(&writer, REFTABLE_BLOCK_TYPE_REF, + (uint8_t *) data.buf, data.len, + 0, hash_size(REFTABLE_HASH_SHA1))); + cl_must_pass(block_writer_add(&writer, &rec)); + cl_assert(block_writer_finish(&writer) > 0); + + block_source_from_buf(&source, &data); + cl_must_pass(reftable_block_init(&block, &source, 0, 0, data.len, + REFTABLE_HASH_SIZE_SHA1, REFTABLE_BLOCK_TYPE_REF)); + + /* + * Corrupt the first restart offset, stored as a big-endian 24-bit + * integer at the start of the restart table, to point past the end of + * the records section. Seeking such a block must fail gracefully. + */ + reftable_put_be24((uint8_t *) block.block_data.data + block.restart_off, + 0xffffff); + + block_iter_init(&it, &block); + cl_must_pass(reftable_buf_addstr(&want, "refs/heads/main")); + cl_assert_equal_i(block_iter_seek_key(&it, &want), REFTABLE_FORMAT_ERROR); + + reftable_buf_release(&want); + block_iter_close(&it); + reftable_block_release(&block); + block_writer_release(&writer); + reftable_buf_release(&data); +}