From: Patrick Steinhardt Date: Wed, 24 Jun 2026 08:23:10 +0000 (+0200) Subject: reftable/block: fix OOB read with bogus restart count X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=91885b31513b41834fcddf293806f2288f4e5ab4;p=thirdparty%2Fgit.git reftable/block: fix OOB read with bogus restart count The restart count is stored in the last two bytes of a block. We use it without verification to compute the offset of the restart table. With a bogus restart count that is large enough this computation underflows, and the subsequent reads via the restart table access out-of-bounds memory: ==129439==ERROR: AddressSanitizer: SEGV on unknown address 0x7d90f6dcd0ad (pc 0x55555598ce89 bp 0x7fffffff4ed0 sp 0x7fffffff4e80 T0) ==129439==The signal is caused by a READ memory access. #0 0x55555598ce89 in reftable_get_be24 ./git/build/../reftable/basics.h:125:9 #1 0x55555598eabf in block_restart_offset ./git/build/../reftable/block.c:407:9 #2 0x55555598e5d5 in restart_needle_less ./git/build/../reftable/block.c:431:17 #3 0x5555559887e2 in binsearch ./git/build/../reftable/basics.c:165:13 #4 0x55555598dfec in block_iter_seek_key ./git/build/../reftable/block.c:529:6 #5 0x555555814517 in test_reftable_block__corrupt_restart_count ./git/build/../t/unit-tests/u-reftable-block.c:593:15 #6 0x5555557f684e in clar_run_test ./git/build/../t/unit-tests/clar/clar.c:335:3 #7 0x5555557f2e69 in clar_run_suite ./git/build/../t/unit-tests/clar/clar.c:431:3 #8 0x5555557f2882 in clar_test_run ./git/build/../t/unit-tests/clar/clar.c:636:4 #9 0x5555557f375f in clar_test ./git/build/../t/unit-tests/clar/clar.c:687:11 #10 0x5555557fa49d in cmd_main ./git/build/../t/unit-tests/unit-test.c:62:8 #11 0x55555584c12a in main ./git/build/../common-main.c:9:11 #12 0x7ffff7a2b284 in __libc_start_call_main (/nix/store/57iz36553175g3178pvxjij8z5rcsd4n-glibc-2.42-61/lib/libc.so.6+0x2b284) (BuildId: 8ae0b698f2d4e727f569f64bb166e08ae30bd077) #13 0x7ffff7a2b337 in __libc_start_main@GLIBC_2.2.5 (/nix/store/57iz36553175g3178pvxjij8z5rcsd4n-glibc-2.42-61/lib/libc.so.6+0x2b337) (BuildId: 8ae0b698f2d4e727f569f64bb166e08ae30bd077) #14 0x555555694c24 in _start (./git/build/t/unit-tests+0x140c24) ==129439==Register values: rax = 0x00007d90f6dcd0ad rbx = 0x00007fffffff4f20 rcx = 0xf2f2f2f8f2f2f2f8 rdx = 0x0000000000000000 rdi = 0x00007d90f6dcd0ad rsi = 0x0000000000007fff rbp = 0x00007fffffff4ed0 rsp = 0x00007fffffff4e80 r8 = 0x0000000000000000 r9 = 0x0000000000000000 r10 = 0x0000000000000000 r11 = 0x0000000000000017 r12 = 0x00007fffffff58e8 r13 = 0x0000000000000001 r14 = 0x00007ffff7ffd000 r15 = 0x00005555560550b0 AddressSanitizer can not provide additional info. SUMMARY: AddressSanitizer: SEGV ./git/build/../reftable/basics.h:125:9 in reftable_get_be24 Verify that the restart table actually fits into the block. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- diff --git a/reftable/block.c b/reftable/block.c index 4d6b11c2e7..4d285aefd7 100644 --- a/reftable/block.c +++ b/reftable/block.c @@ -351,6 +351,10 @@ int reftable_block_init(struct reftable_block *block, restart_count = reftable_get_be16(block->block_data.data + block_size - 2); restart_off = block_size - 2 - 3 * restart_count; + if (restart_off < header_size + 4 || restart_off > block_size - 2) { + err = REFTABLE_FORMAT_ERROR; + goto done; + } block->block_type = block_type; block->hash_size = hash_size; diff --git a/t/unit-tests/u-reftable-block.c b/t/unit-tests/u-reftable-block.c index 1f35aed91a..ba410a0885 100644 --- a/t/unit-tests/u-reftable-block.c +++ b/t/unit-tests/u-reftable-block.c @@ -545,3 +545,49 @@ void test_reftable_block__corrupt_block_size(void) block_writer_release(&writer); reftable_buf_release(&data); } + +void test_reftable_block__corrupt_restart_count(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 reftable_buf data; + int block_size; + + 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)); + block_size = block_writer_finish(&writer); + cl_assert(block_size > 0); + + /* + * Corrupt the restart count to claim a bogus number of restart points. + * Note that this would only cause us to perform an out-of-bounds + * access when seeking into the block, but we want to refuse such a + * block outright. + */ + reftable_put_be16((uint8_t *) data.buf + block_size - 2, 0xffff); + + block_source_from_buf(&source, &data); + cl_assert_equal_i(reftable_block_init(&block, &source, 0, 0, data.len, + REFTABLE_HASH_SIZE_SHA1, REFTABLE_BLOCK_TYPE_REF), + REFTABLE_FORMAT_ERROR); + + reftable_block_release(&block); + block_writer_release(&writer); + reftable_buf_release(&data); +}