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

[PATCH 4/6] cat-file: use size_t for the length passed to batch_write()

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

batch_write() takes the length of the data as an "int", but its callers pass the size of whole objects as returned by odb_read_object() or filter_object(), which is a size_t. For an object between 2GiB and 4GiB the length wraps to a negative value, which fwrite() and write_or_die() then see as an enormous size_t. We happily write the object and then keep going past the end of the buffer, sending whatever memory follows to stdout, until we hit an unmapped page and die with "write error: Bad address". Objects of 4GiB and more are truncated instead, which desynchronizes the --batch output.

This affects all platforms, not only those with a 32-bit "long". It can be triggered with "--batch" on any non-blob object, or on blobs with --filters or --textconv, as blobs are otherwise streamed. For example:

	blob=$(test-tool genzeros $((3 * 1024 * 1024 * 1024)) |
	       git hash-object -w --stdin) &&
	echo "$blob big" |
	git cat-file --batch="%(objectname) %(rest)" --filters >out

fails with "write error: Bad address"; with stdout going to /dev/null, strace shows write() being called on addresses far beyond the end of the 3GiB buffer. There is no test, as it would need several GiB of memory and disk.

Both fwrite() and write_or_die() take a size_t already, so just change the parameter type.

Signed-off-by: Elia Pinto <gitter.spiros@gmail.com>
---
 builtin/cat-file.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/builtin/cat-file.c b/builtin/cat-file.c
index 8870a210ec..c846fe3116 100644
--- a/builtin/cat-file.c
+++ b/builtin/cat-file.c
@@ -425,7 +425,7 @@ static void expand_format(struct strbuf *sb, const char *start,
 	}
 }
 
-static void batch_write(struct batch_options *opt, const void *data, int len)
+static void batch_write(struct batch_options *opt, const void *data, size_t len)
 {
 	if (opt->buffer_output) {
 		if (fwrite(data, 1, len, stdout) != len)
-- 
2.53.0
Previous: Elia PintoNext: Elia Pinto
Message 5 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.