]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Merge branch 'ps/reftable-hardening' into jch
authorJunio C Hamano <gitster@pobox.com>
Tue, 30 Jun 2026 19:59:13 +0000 (12:59 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 30 Jun 2026 19:59:14 +0000 (12:59 -0700)
The reftable code has been hardened against corrupted tables by
fixing out-of-bounds writes, out-of-bounds reads, and abort calls
during parsing.

* ps/reftable-hardening:
  reftable/table: fix OOB read on truncated table
  reftable/table: fix NULL pointer access when seeking to bogus offsets
  reftable/block: fix OOB read with bogus restart offset
  reftable/block: fix use of uninitialized memory when binsearch fails
  reftable/block: fix OOB read with bogus restart count
  reftable/block: fix OOB read with bogus block size
  reftable/block: fix OOB write with bogus inflated log size
  t/unit-tests: introduce test helper to write reftable blocks
  reftable/record: don't abort when decoding invalid ref value type
  reftable/basics: fix OOB read on binary search of empty range
  oss-fuzz: add fuzzer for parsing reftables
  meson: support building fuzzers with libFuzzer

1  2 
Makefile
meson.build
meson_options.txt
t/unit-tests/u-reftable-table.c

diff --cc Makefile
Simple merge
diff --cc meson.build
Simple merge
Simple merge
index fae478ee044c487dac5aaf13a2a644f90de445f4,28b0ef5258a3f06dcf46d58c0fcb6712beca1e5f..d4251fae6fb09a9c9a8868f944d864b22551d296
@@@ -201,3 -202,91 +204,92 @@@ void test_reftable_table__block_iterato
        reftable_buf_release(&buf);
        reftable_free(records);
  }
 -                               logs, ARRAY_SIZE(logs), NULL);
+ void test_reftable_table__seek_invalid_log_offset(void)
+ {
+       struct reftable_ref_record refs[] = {
+               {
+                       .refname = (char *) "refs/heads/main",
+                       .value_type = REFTABLE_REF_VAL1,
+                       .value.val1 = { 42 },
+               },
+       };
+       struct reftable_log_record logs[] = {
+               {
+                       .refname = (char *) "refs/heads/main",
+                       .update_index = 1,
+                       .value_type = REFTABLE_LOG_UPDATE,
+                       .value.update = {
+                               .name = (char *) "user",
+                               .email = (char *) "user@example.com",
+                               .message = (char *) "message\n",
+                       },
+               },
+       };
+       struct reftable_block_source source = { 0 };
+       struct reftable_log_record log = { 0 };
+       struct reftable_iterator it = { 0 };
+       struct reftable_table *table;
+       struct reftable_buf buf = REFTABLE_BUF_INIT;
+       size_t fsize = footer_size(1);
+       uint8_t *footer;
+       cl_reftable_write_to_buf(&buf, refs, ARRAY_SIZE(refs),
 -      cl_reftable_write_to_buf(&buf, refs, ARRAY_SIZE(refs), NULL, 0, NULL);
++                               logs, ARRAY_SIZE(logs), REFTABLE_HASH_SHA1, NULL);
+       /*
+        * Corrupt the log section offset stored in the footer so that it points
+        * past the end of the table. The footer is checksummed, so we also have
+        * to recompute and rewrite the CRC.
+        */
+       footer = (uint8_t *) buf.buf + buf.len - fsize;
+       reftable_put_be64(footer + header_size(1) + 24, UINT64_MAX);
+       reftable_put_be32(footer + fsize - 4, crc32(0, footer, fsize - 4));
+       block_source_from_buf(&source, &buf);
+       cl_must_pass(reftable_table_new(&table, &source, "name"));
+       /*
+        * Seeking the log iterator must not crash even though the log section
+        * offset is bogus. As the offset points past the end of the table we
+        * know that the table is corrupt, so the seek must report a format
+        * error instead of pretending that the section is empty.
+        */
+       reftable_table_init_log_iterator(table, &it);
+       cl_assert_equal_i(reftable_iterator_seek_log(&it, ""),
+                         REFTABLE_FORMAT_ERROR);
+       reftable_log_record_release(&log);
+       reftable_iterator_destroy(&it);
+       reftable_table_decref(table);
+       reftable_buf_release(&buf);
+ }
+ void test_reftable_table__new_with_truncated_table(void)
+ {
+       struct reftable_ref_record refs[] = {
+               {
+                       .refname = (char *) "refs/heads/main",
+                       .value_type = REFTABLE_REF_VAL1,
+                       .value.val1 = { 42 },
+               },
+       };
+       struct reftable_block_source source = { 0 };
+       struct reftable_table *table;
+       struct reftable_buf buf = REFTABLE_BUF_INIT;
++      cl_reftable_write_to_buf(&buf, refs, ARRAY_SIZE(refs), NULL, 0,
++                               REFTABLE_HASH_SHA1, NULL);
+       /*
+        * Truncate the table so that it is large enough to read the header, but
+        * too small to also contain the footer.
+        */
+       buf.len = footer_size(1) - 1;
+       block_source_from_buf(&source, &buf);
+       cl_assert_equal_i(reftable_table_new(&table, &source, "name"),
+                         REFTABLE_FORMAT_ERROR);
+       reftable_buf_release(&buf);
+ }