Git development
 help / color / mirror / Atom feed
From: "Matthew Suozzo via GitGitGadget" <gitgitgadget@gmail.com>
To: git@vger.kernel.org
Cc: Patrick Steinhardt <ps@pks.im>, Matthew Suozzo <m@suozzo.co>,
	Matthew Suozzo <m@suozzo.co>
Subject: [PATCH] reftable/record: bound an object record's offset count by its length
Date: Wed, 14 Oct 2026 14:58:03 +0000	[thread overview]
Message-ID: <pull.2255.git.1791989883899.gitgitgadget@gmail.com> (raw)

From: Matthew Suozzo <m@suozzo.co>

An object record's count of offsets is a 64-bit varint from the table,
and reftable_obj_record_decode() allocates the offsets array from it
through REFTABLE_ALLOC_ARRAY(), which takes a size_t. Where size_t is
32 bits, a count of 2^32 + 1 allocates room for one offset, and the
loop, which runs to the full count, writes an offset from the table
for each varint left in the block past the end of the array. Where
size_t is 64 bits, a large count only makes a large allocation that
the decode then fails to fill.

Nothing in Git reaches this decoder from a repository: a block of a
type other than the one wanted is refused before its records are
decoded, and the object index, the only reader of object blocks, is
used only by the unit tests. The decoder is still wrong.

Each offset takes at least a byte, so refuse a count larger than what
is left of the record.

The Makefile did not build the reftable record unit tests (the Meson
build does). Add the suite there so that the new test runs in both.

Assisted-by: Claude Opus 5.5, Claude Fable 5.1
Signed-off-by: Matthew Suozzo <m@suozzo.co>
---
    reftable/record: bound an object record's offset count by its length

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-2255%2Fmsuozzo%2Freftable-obj-record-count-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2255/msuozzo/reftable-obj-record-count-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/2255

 Makefile                         |  1 +
 reftable/record.c                |  4 ++++
 t/unit-tests/u-reftable-record.c | 35 ++++++++++++++++++++++++++++++++
 3 files changed, 40 insertions(+)

diff --git a/Makefile b/Makefile
index a96be506b5..66b7270fc0 100644
--- a/Makefile
+++ b/Makefile
@@ -1551,6 +1551,7 @@ CLAR_TEST_SUITES += u-reftable-block
 CLAR_TEST_SUITES += u-reftable-merged
 CLAR_TEST_SUITES += u-reftable-pq
 CLAR_TEST_SUITES += u-reftable-readwrite
+CLAR_TEST_SUITES += u-reftable-record
 CLAR_TEST_SUITES += u-reftable-stack
 CLAR_TEST_SUITES += u-reftable-table
 CLAR_TEST_SUITES += u-reftable-tree
diff --git a/reftable/record.c b/reftable/record.c
index 1fce441930..96eceddca7 100644
--- a/reftable/record.c
+++ b/reftable/record.c
@@ -589,6 +589,10 @@ static int reftable_obj_record_decode(void *rec, struct reftable_buf key,
 	if (count == 0)
 		return start.len - in.len;
 
+	/* each offset takes at least a byte */
+	if (count > in.len)
+		return REFTABLE_FORMAT_ERROR;
+
 	REFTABLE_ALLOC_ARRAY(r->offsets, count);
 	if (!r->offsets)
 		return REFTABLE_OUT_OF_MEMORY_ERROR;
diff --git a/t/unit-tests/u-reftable-record.c b/t/unit-tests/u-reftable-record.c
index 9c95083ef4..0165b74bcb 100644
--- a/t/unit-tests/u-reftable-record.c
+++ b/t/unit-tests/u-reftable-record.c
@@ -524,6 +524,41 @@ void test_reftable_record__obj_record_roundtrip(void)
 	reftable_buf_release(&scratch);
 }
 
+void test_reftable_record__obj_record_decode_count_larger_than_record(void)
+{
+	struct reftable_buf key = REFTABLE_BUF_INIT, scratch = REFTABLE_BUF_INIT;
+	struct reftable_record rec = { .type = REFTABLE_BLOCK_TYPE_OBJ };
+	uint8_t buffer[32];
+	struct string_view dest = { .buf = buffer, .len = sizeof(buffer) }, in;
+	int n;
+
+	/*
+	 * A count of offsets that a 32-bit size_t truncates to one,
+	 * followed by two offsets.
+	 */
+	n = put_var_int(&dest, ((uint64_t)1 << 32) + 1);
+	cl_assert_gt_i(n, 0);
+	string_view_consume(&dest, n);
+	n = put_var_int(&dest, 1);
+	cl_assert_gt_i(n, 0);
+	string_view_consume(&dest, n);
+	n = put_var_int(&dest, 1);
+	cl_assert_gt_i(n, 0);
+	string_view_consume(&dest, n);
+
+	in.buf = buffer;
+	in.len = sizeof(buffer) - dest.len;
+
+	cl_assert_equal_i(reftable_buf_addstr(&key, "\x01\x02"), 0);
+	cl_assert_equal_i(reftable_record_decode(&rec, key, 0, in,
+						 REFTABLE_HASH_SIZE_SHA1, &scratch),
+			  REFTABLE_FORMAT_ERROR);
+
+	reftable_record_release(&rec);
+	reftable_buf_release(&key);
+	reftable_buf_release(&scratch);
+}
+
 void test_reftable_record__index_record_comparison(void)
 {
 	struct reftable_record in[3] = {

base-commit: 6de20f6092dcf9bdb1c8efe03db4b70c82b423dd
-- 
gitgitgadget

                 reply	other threads:[~2026-10-14 14:58 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=pull.2255.git.1791989883899.gitgitgadget@gmail.com \
    --to=gitgitgadget@gmail.com \
    --cc=git@vger.kernel.org \
    --cc=m@suozzo.co \
    --cc=ps@pks.im \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox