git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH 3/6] odb/source-loose: check object size when streaming loose objects

From
Elia Pinto <gitter.spiros@gmail.com>
Date
Oct 30, 2026, 10:28 UTC
Message-ID
<dee492c3d8dd58196f0524f7b682ab08ad2b066f.1793355390.git.gitter.spiros@gmail.com>
In-Reply-To
<cover.1793355390.git.gitter.spiros@gmail.com>

read_istream_loose() inflates a loose object until zlib reports the end of the stream, but never compares the number of bytes it produced with the size given in the object header. A corrupt object whose stream is shorter than its header claims is thus streamed out without any error, e.g. "git cat-file blob" exits with 0 after writing fewer bytes than "git cat-file -s" reports. The same happens if the stream is longer than the header claims.

Keep track of how many bytes we handed out and report the object as corrupt if that does not match the size from the header once the stream ends.

The tests create such objects with the loose_obj() helper, which can already write a bogus size into the header.

Signed-off-by: Elia Pinto <gitter.spiros@gmail.com>
---
 odb/source-loose.c  | 14 ++++++++++++++
 t/t1006-cat-file.sh | 18 ++++++++++++++++++
 2 files changed, 32 insertions(+)
diff --git a/odb/source-loose.c b/odb/source-loose.c
index d77e93e7c4..2233714ac4 100644
--- a/odb/source-loose.c
+++ b/odb/source-loose.c
@@ -289,6 +289,8 @@ struct odb_loose_read_stream {
 	char hdr[32];
 	int hdr_avail;
 	int hdr_used;
+	size_t total_read;
+	struct object_id oid;
 };
 
 static ssize_t read_istream_loose(struct odb_stream *_st, char *buf, size_t sz)
@@ -326,6 +328,16 @@ static ssize_t read_istream_loose(struct odb_stream *_st, char *buf, size_t sz)
 
 		if (status == Z_STREAM_END) {
 			git_inflate_end(&st->z);
+			/*
+			 * The object must be exactly as large as its
+			 * header claims.
+			 */
+			if (st->total_read + total_read != st->base.size) {
+				error(_("corrupt loose object '%s'"),
+				      oid_to_hex(&st->oid));
+				st->z_state = ODB_LOOSE_READ_STREAM_ERROR;
+				return -1;
+			}
 			st->z_state = ODB_LOOSE_READ_STREAM_DONE;
 			break;
 		}
@@ -335,6 +347,7 @@ static ssize_t read_istream_loose(struct odb_stream *_st, char *buf, size_t sz)
 			return -1;
 		}
 	}
+	st->total_read += total_read;
 	return total_read;
 }
 
@@ -388,6 +401,7 @@ static int odb_source_loose_read_object_stream(struct odb_stream **out,
 
 	st->mapped = mapped;
 	st->mapsize = mapsize;
+	oidcpy(&st->oid, oid);
 	st->hdr_used = strlen(st->hdr) + 1;
 	st->hdr_avail = st->z.total_out;
 	st->z_state = ODB_LOOSE_READ_STREAM_INUSE;
diff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh
index 72fc8acc61..31a76c1b5d 100755
--- a/t/t1006-cat-file.sh
+++ b/t/t1006-cat-file.sh
@@ -1481,4 +1481,22 @@ test_objects_filter "object:type=commit"
 test_objects_filter "object:type=tag"
 test_objects_filter "object:type=tree"
 
+test_expect_success 'setup blobs with bogus size in header' '
+	git init bogus-size &&
+	echo_without_newline short |
+	loose_obj bogus-size/.git/objects blob 100 >oid-too-big &&
+	echo_without_newline "much longer" |
+	loose_obj bogus-size/.git/objects blob 3 >oid-too-small
+'
+
+test_expect_success 'streaming blob shorter than its header fails' '
+	test_must_fail git -C bogus-size cat-file blob $(cat oid-too-big) >out 2>err &&
+	test_grep "corrupt loose object" err
+'
+
+test_expect_success 'streaming blob longer than its header fails' '
+	test_must_fail git -C bogus-size cat-file blob $(cat oid-too-small) >out 2>err &&
+	test_grep "corrupt loose object" err
+'
+
 test_done
-- 
2.53.0
Previous: Elia PintoNext: Elia Pinto
Message 4 of 7 in “loose object size checks and a few size truncations”
  1. 0/6 loose object size checks and a few size truncationsElia Pinto, Oct 30, 2026
  2. 1/6 object-file: check size of inflated loose object against its headerElia Pinto, Oct 30, 2026
  3. 2/6 object-file: use size_t for loose object sizesElia Pinto, Oct 30, 2026
  4. 3/6 odb/source-loose: check object size when streaming loose objectsElia Pinto, Oct 30, 2026
  5. 4/6 cat-file: use size_t for the length passed to batch_write()Elia Pinto, Oct 30, 2026
  6. 5/6 notes: avoid truncating the size of concatenated notesElia Pinto, Oct 30, 2026
  7. 6/6 diff: use size_t in count_trailing_blank()Elia Pinto, Oct 30, 2026

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.