From: Elia Pinto Date: Fri, 30 Oct 2026 10:28:36 GMT Subject: [PATCH 3/6] odb/source-loose: check object size when streaming loose objects Message-ID: In-Reply-To: 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 --- 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