]> git.ipfire.org Git - thirdparty/git.git/commitdiff
reftable/block: fix OOB read with bogus restart count
authorPatrick Steinhardt <ps@pks.im>
Wed, 24 Jun 2026 08:23:10 +0000 (10:23 +0200)
committerJunio C Hamano <gitster@pobox.com>
Wed, 24 Jun 2026 16:30:25 +0000 (09:30 -0700)
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 <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
reftable/block.c
t/unit-tests/u-reftable-block.c

index 4d6b11c2e7e660d52f206845a84359f58946a3e8..4d285aefd7b93aacc0bbaa3ead18890f85b7491b 100644 (file)
@@ -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;
index 1f35aed91ae5de0418ff432e2da21f17761ab099..ba410a0885230c747541d218738c02f2cb424bea 100644 (file)
@@ -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);
+}