Git development
 help / color / mirror / Atom feed
* [PATCH 0/4] use size_t when receiving and verifying packs
@ 2026-10-31 11:39 Elia Pinto
  2026-10-31 11:39 ` [PATCH 1/4] unpack-objects: use size_t for object sizes Elia Pinto
                   ` (3 more replies)
  0 siblings, 4 replies; 5+ messages in thread
From: Elia Pinto @ 2026-10-31 11:39 UTC (permalink / raw)
  To: git
  Cc: Johannes Schindelin, Elia Pinto, Justin Tobler, Han Xin, Han Xin,
	Ævar Arnfjörð Bjarmason, Patrick Steinhardt,
	Junio C Hamano, Jonathan Tan

This series continues the search for truncated sizes, this time focusing
on the side that receives and verifies packs: unpack-objects,
index-pack, and pack verification in git fsck. These components keep
object sizes as "unsigned long", while the underlying pack code already
uses size_t.

Each patch addresses a specific part of the pack pipeline:
 - 1/4 unpack-objects: on Windows, it dies when receiving an object of
   4GiB or more;
 - 2/4 index-pack write_compressed(): on all platforms ("unsigned int"),
   --fix-thin with a local base of 4GiB or more dies with "serious
   inflate inconsistency", verified by simulating at 16 bits;
 - 3/4 index-pack: on Windows, it triggers a false "SHA1 COLLISION
   FOUND" when the object already exists locally;
 - 4/4 pack-check: on Windows, "git fsck" checks a prefix of objects in
   packs.

These are all failures (die) or checks on a prefix; no corrupt data ends
up being written.

No tests are added, for the same reason as the object parsing series
(requiring 4GiB objects and 32-bit long platforms, or objects of 4GiB on
all platforms for 2/4).

This series closes the pack-verification boundary left open for
fsck_object() by the object parsing and checking series:
  https://lore.kernel.org/git/cover.1793444420.git.gitter.spiros@gmail.com/
It does not depend on other in-flight series and merges cleanly with
them. For the full effect on non-streamed objects in 1/4, the downstream
fsck_object() / parse_object_buffer() (from the series above) and
odb_write_object() (from v3 of "use size_t for a few more object and
buffer sizes") are needed as well:
  https://lore.kernel.org/git/cover.1793439137.git.gitter.spiros@gmail.com/
Pack-objects (delta search below core.bigFileThreshold) is left for a
later series.

Based on master 50fe5cc63f.

Elia Pinto (4):
  unpack-objects: use size_t for object sizes
  index-pack: use size_t in write_compressed()
  index-pack: use size_t for object sizes
  pack-check: use size_t for the size passed to verify callbacks

 builtin/fsck.c           |  2 +-
 builtin/index-pack.c     | 14 +++++++-------
 builtin/unpack-objects.c | 36 +++++++++++++++++-------------------
 odb.h                    |  2 +-
 pack.h                   |  2 +-
 5 files changed, 27 insertions(+), 29 deletions(-)


base-commit: 50fe5cc63f9cd6e89b87a2c9ccf96a444f4cfdb7
-- 
2.53.0


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH 1/4] unpack-objects: use size_t for object sizes
  2026-10-31 11:39 [PATCH 0/4] use size_t when receiving and verifying packs Elia Pinto
@ 2026-10-31 11:39 ` Elia Pinto
  2026-10-31 11:39 ` [PATCH 2/4] index-pack: use size_t in write_compressed() Elia Pinto
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 5+ messages in thread
From: Elia Pinto @ 2026-10-31 11:39 UTC (permalink / raw)
  To: git
  Cc: Johannes Schindelin, Elia Pinto, Patrick Steinhardt,
	Justin Tobler, Junio C Hamano, Han Xin, Han Xin,
	Ævar Arnfjörð Bjarmason

builtin/unpack-objects.c uses "unsigned long" to track object sizes
throughout the file, while sizes read from packs are handled as size_t.
On platforms where "long" is 32 bits, this truncates the size for
objects of 4GiB or more.

When handling large objects on those platforms, unpack-objects fails
rather than corrupting data. For instance, streaming large blobs
(exceeding core.bigFileThreshold) passes a truncated size to
stream_blob(), causing the stream write to fail with an error.

Use size_t across the board in builtin/unpack-objects.c and remove the
now-unnecessary size cast in unpack_delta_entry(). Downstream consumers
like odb_write_object() and fsck_object() are updated separately.

Triggering this requires processing 4GiB or more of input on a platform
where "long" is 32 bits, so no test is added.

Signed-off-by: Elia Pinto <gitter.spiros@gmail.com>
---
 builtin/unpack-objects.c | 36 +++++++++++++++++-------------------
 1 file changed, 17 insertions(+), 19 deletions(-)

diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c
index 351948724a..6c830a606f 100644
--- a/builtin/unpack-objects.c
+++ b/builtin/unpack-objects.c
@@ -40,7 +40,7 @@ static struct progress *progress;
  */
 struct obj_buffer {
 	char *buffer;
-	unsigned long size;
+	size_t size;
 };
 
 static struct decoration obj_decorate;
@@ -50,7 +50,7 @@ static struct obj_buffer *lookup_object_buffer(struct object *base)
 	return lookup_decoration(&obj_decorate, base);
 }
 
-static void add_object_buffer(struct object *object, char *buffer, unsigned long size)
+static void add_object_buffer(struct object *object, char *buffer, size_t size)
 {
 	struct obj_buffer *obj;
 	CALLOC_ARRAY(obj, 1);
@@ -114,10 +114,10 @@ static void use(int bytes)
  * allocated buffer which is reused to hold temporary zstream output
  * and return NULL instead of returning garbage data.
  */
-static void *get_data(unsigned long size)
+static void *get_data(size_t size)
 {
 	git_zstream stream;
-	unsigned long bufsize = dry_run && size > 8192 ? 8192 : size;
+	size_t bufsize = dry_run && size > 8192 ? 8192 : size;
 	void *buf = xmallocz(bufsize);
 
 	memset(&stream, 0, sizeof(stream));
@@ -161,7 +161,7 @@ struct delta_info {
 	struct object_id base_oid;
 	unsigned nr;
 	off_t base_offset;
-	unsigned long size;
+	size_t size;
 	void *delta;
 	struct delta_info *next;
 };
@@ -170,7 +170,7 @@ static struct delta_info *delta_list;
 
 static void add_delta_to_list(unsigned nr, const struct object_id *base_oid,
 			      off_t base_offset,
-			      void *delta, unsigned long size)
+			      void *delta, size_t size)
 {
 	struct delta_info *info = xmalloc(sizeof(*info));
 
@@ -261,7 +261,7 @@ static void write_rest(void)
 }
 
 static void added_object(unsigned nr, enum object_type type,
-			 void *data, unsigned long size);
+			 void *data, size_t size);
 
 /*
  * Write out nr-th object from the list, now we know the contents
@@ -269,7 +269,7 @@ static void added_object(unsigned nr, enum object_type type,
  * to be checked at the end.
  */
 static void write_object(unsigned nr, enum object_type type,
-			 void *buf, unsigned long size)
+			 void *buf, size_t size)
 {
 	if (!strict) {
 		if (odb_write_object(the_repository->objects, buf, size, type,
@@ -310,8 +310,8 @@ static void write_object(unsigned nr, enum object_type type,
 }
 
 static void resolve_delta(unsigned nr, enum object_type type,
-			  void *base, unsigned long base_size,
-			  void *delta, unsigned long delta_size)
+			  void *base, size_t base_size,
+			  void *delta, size_t delta_size)
 {
 	void *result;
 	size_t result_size;
@@ -330,7 +330,7 @@ static void resolve_delta(unsigned nr, enum object_type type,
  * resolve all the deltified objects that are based on it.
  */
 static void added_object(unsigned nr, enum object_type type,
-			 void *data, unsigned long size)
+			 void *data, size_t size)
 {
 	struct delta_info **p = &delta_list;
 	struct delta_info *info;
@@ -349,7 +349,7 @@ static void added_object(unsigned nr, enum object_type type,
 	}
 }
 
-static void unpack_non_delta_entry(enum object_type type, unsigned long size,
+static void unpack_non_delta_entry(enum object_type type, size_t size,
 				   unsigned nr)
 {
 	void *buf = get_data(size);
@@ -386,7 +386,7 @@ static ssize_t zlib_stream_read(struct odb_stream *in_stream,
 	return buf_len - zstream->avail_out;
 }
 
-static void stream_blob(unsigned long size, unsigned nr)
+static void stream_blob(size_t size, unsigned nr)
 {
 	git_zstream zstream = { 0 };
 	struct zlib_stream in_stream = {
@@ -420,7 +420,7 @@ static void stream_blob(unsigned long size, unsigned nr)
 }
 
 static int resolve_against_held(unsigned nr, const struct object_id *base,
-				void *delta_data, unsigned long delta_size)
+				void *delta_data, size_t delta_size)
 {
 	struct object *obj;
 	struct obj_buffer *obj_buffer;
@@ -435,12 +435,11 @@ static int resolve_against_held(unsigned nr, const struct object_id *base,
 	return 1;
 }
 
-static void unpack_delta_entry(enum object_type type, unsigned long delta_size,
+static void unpack_delta_entry(enum object_type type, size_t delta_size,
 			       unsigned nr)
 {
 	void *delta_data, *base;
-	unsigned long base_size;
-	size_t base_size_st = 0;
+	size_t base_size = 0;
 	struct object_id base_oid;
 
 	if (type == OBJ_REF_DELTA) {
@@ -517,8 +516,7 @@ static void unpack_delta_entry(enum object_type type, unsigned long delta_size,
 		return;
 
 	base = odb_read_object(the_repository->objects, &base_oid,
-			       &type, &base_size_st);
-	base_size = cast_size_t_to_ulong(base_size_st);
+			       &type, &base_size);
 	if (!base) {
 		error("failed to read delta-pack base object %s",
 		      oid_to_hex(&base_oid));
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH 2/4] index-pack: use size_t in write_compressed()
  2026-10-31 11:39 [PATCH 0/4] use size_t when receiving and verifying packs Elia Pinto
  2026-10-31 11:39 ` [PATCH 1/4] unpack-objects: use size_t for object sizes Elia Pinto
@ 2026-10-31 11:39 ` Elia Pinto
  2026-10-31 11:39 ` [PATCH 3/4] index-pack: use size_t for object sizes Elia Pinto
  2026-10-31 11:39 ` [PATCH 4/4] pack-check: use size_t for the size passed to verify callbacks Elia Pinto
  3 siblings, 0 replies; 5+ messages in thread
From: Elia Pinto @ 2026-10-31 11:39 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Elia Pinto, Junio C Hamano

write_compressed() in builtin/index-pack.c takes the object size as an
"unsigned int" and returns an "int", which are 32 bits wide on all
platforms. It is used by append_obj_to_pack() when resolving thin packs
with --fix-thin.

With a local base of 4GiB or more, write_compressed() compresses only
the first "size mod 2^32" bytes, while the pack header written by
append_obj_to_pack() records the full size. Later, when resolving deltas
against that base, unpack_data() encounters an early end of stream and
dies with "serious inflate inconsistency". Shrinking the parameter to 16
bits and using a 100KiB base makes "git index-pack --fix-thin" die the
same way.

Use size_t for both the size parameter and the return value.

Triggering this requires processing a local base of 4GiB or more, so
no test is added.

Signed-off-by: Elia Pinto <gitter.spiros@gmail.com>
---
 builtin/index-pack.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/builtin/index-pack.c b/builtin/index-pack.c
index 0264c18c5f..f65228468b 100644
--- a/builtin/index-pack.c
+++ b/builtin/index-pack.c
@@ -1411,7 +1411,7 @@ static void conclude_pack(int fix_thin_pack, const char *curr_pack, unsigned cha
 		    nr_ofs_deltas + nr_ref_deltas - nr_resolved_deltas);
 }
 
-static int write_compressed(struct hashfile *f, void *in, unsigned int size)
+static size_t write_compressed(struct hashfile *f, void *in, size_t size)
 {
 	git_zstream stream;
 	int status;
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH 3/4] index-pack: use size_t for object sizes
  2026-10-31 11:39 [PATCH 0/4] use size_t when receiving and verifying packs Elia Pinto
  2026-10-31 11:39 ` [PATCH 1/4] unpack-objects: use size_t for object sizes Elia Pinto
  2026-10-31 11:39 ` [PATCH 2/4] index-pack: use size_t in write_compressed() Elia Pinto
@ 2026-10-31 11:39 ` Elia Pinto
  2026-10-31 11:39 ` [PATCH 4/4] pack-check: use size_t for the size passed to verify callbacks Elia Pinto
  3 siblings, 0 replies; 5+ messages in thread
From: Elia Pinto @ 2026-10-31 11:39 UTC (permalink / raw)
  To: git
  Cc: Johannes Schindelin, Elia Pinto, Jonathan Tan, Junio C Hamano,
	Patrick Steinhardt

Functions like sha1_object() and append_obj_to_pack() in
builtin/index-pack.c take object sizes as an "unsigned long", even
though struct object_entry already tracks them as size_t. On platforms
where "long" is 32 bits, this truncates the size for objects of 4GiB or
more.

When receiving, on those platforms, a large object that already exists
locally, sha1_object() compares the real size (size_t) against the
truncated size, resulting in a false "SHA1 COLLISION FOUND" error and
aborting index-pack.

Use size_t for the size parameter across these functions.

Triggering this requires processing 4GiB or more of input on a platform
where "long" is 32 bits, so no test is added.

Signed-off-by: Elia Pinto <gitter.spiros@gmail.com>
---
 builtin/index-pack.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/builtin/index-pack.c b/builtin/index-pack.c
index f65228468b..d4c073654d 100644
--- a/builtin/index-pack.c
+++ b/builtin/index-pack.c
@@ -588,7 +588,7 @@ static void *unpack_raw_entry(struct object_entry *obj,
 }
 
 static void *unpack_data(struct object_entry *obj,
-			 int (*consume)(const unsigned char *, unsigned long, void *),
+			 int (*consume)(const unsigned char *, size_t, void *),
 			 void *cb_data)
 {
 	off_t from = obj[0].idx.offset + obj[0].hdr_size;
@@ -765,10 +765,10 @@ struct compare_data {
 	struct object_entry *entry;
 	struct odb_stream *st;
 	unsigned char *buf;
-	unsigned long buf_size;
+	size_t buf_size;
 };
 
-static int compare_objects(const unsigned char *buf, unsigned long size,
+static int compare_objects(const unsigned char *buf, size_t size,
 			   void *cb_data)
 {
 	struct compare_data *data = cb_data;
@@ -881,7 +881,7 @@ static void do_record_outgoing_links(struct object *obj)
 }
 
 static void sha1_object(const void *data, struct object_entry *obj_entry,
-			unsigned long size, enum object_type type,
+			size_t size, enum object_type type,
 			const struct object_id *oid)
 {
 	void *new_data = NULL;
@@ -1438,11 +1438,11 @@ static size_t write_compressed(struct hashfile *f, void *in, size_t size)
 
 static struct object_entry *append_obj_to_pack(struct hashfile *f,
 			       const unsigned char *sha1, void *buf,
-			       unsigned long size, enum object_type type)
+			       size_t size, enum object_type type)
 {
 	struct object_entry *obj = &objects[nr_objects++];
 	unsigned char header[10];
-	unsigned long s = size;
+	size_t s = size;
 	int n = 0;
 	unsigned char c = (type << 4) | (s & 15);
 	s >>= 4;
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH 4/4] pack-check: use size_t for the size passed to verify callbacks
  2026-10-31 11:39 [PATCH 0/4] use size_t when receiving and verifying packs Elia Pinto
                   ` (2 preceding siblings ...)
  2026-10-31 11:39 ` [PATCH 3/4] index-pack: use size_t for object sizes Elia Pinto
@ 2026-10-31 11:39 ` Elia Pinto
  3 siblings, 0 replies; 5+ messages in thread
From: Elia Pinto @ 2026-10-31 11:39 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Elia Pinto, Junio C Hamano,
	Patrick Steinhardt

verify_pack() in pack-check.c passes each object to a verify_fn callback
(defined in pack.h) taking the object size as an "unsigned long", even
though pack-check already handles it as a size_t. This signature is also
replicated in struct odb_fsck_options (odb.h) and implemented by
fsck_obj_buffer() in builtin/fsck.c.

On platforms where "long" is 32 bits, running "git fsck" on packs with
objects of 4GiB or more passes a truncated size to fsck_obj_buffer(),
causing parse_object_buffer() and fsck_object() to inspect only a
prefix.

Use size_t for the size parameter in verify_fn,
struct odb_fsck_options.object_cb, and fsck_obj_buffer().

Triggering this requires processing objects of 4GiB or more on a
platform where "long" is 32 bits, so no test is added.

Signed-off-by: Elia Pinto <gitter.spiros@gmail.com>
---
 builtin/fsck.c | 2 +-
 odb.h          | 2 +-
 pack.h         | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/builtin/fsck.c b/builtin/fsck.c
index da90cf84bf..9f62aa1f4e 100644
--- a/builtin/fsck.c
+++ b/builtin/fsck.c
@@ -390,7 +390,7 @@ static void check_connectivity(struct repository *repo)
 }
 
 static int fsck_obj_buffer(const struct object_id *oid, enum object_type type,
-			   unsigned long size, void *buffer, int *eaten, void *cb_data)
+			   size_t size, void *buffer, int *eaten, void *cb_data)
 {
 	struct repository *repo = cb_data;
 	struct object *obj;
diff --git a/odb.h b/odb.h
index dd85b880a7..9cb36ed5ad 100644
--- a/odb.h
+++ b/odb.h
@@ -197,7 +197,7 @@ struct odb_fsck_options {
 	enum odb_fsck_flags flags;
 
 	int (*object_cb)(const struct object_id *oid, enum object_type type,
-			 unsigned long size, void *buffer, int *eaten, void *cb_data);
+			 size_t size, void *buffer, int *eaten, void *cb_data);
 	void *object_payload;
 };
 
diff --git a/pack.h b/pack.h
index ada506b5c5..d9649c6ee0 100644
--- a/pack.h
+++ b/pack.h
@@ -88,7 +88,7 @@ struct progress;
 /* Note, the data argument could be NULL if object type is blob */
 typedef int (*verify_fn)(const struct object_id *oid,
 			 enum object_type type,
-			 unsigned long size,
+			 size_t size,
 			 void *buffer, int *eaten,
 			 void *fn_data);
 
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-10-31 11:39 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-31 11:39 [PATCH 0/4] use size_t when receiving and verifying packs Elia Pinto
2026-10-31 11:39 ` [PATCH 1/4] unpack-objects: use size_t for object sizes Elia Pinto
2026-10-31 11:39 ` [PATCH 2/4] index-pack: use size_t in write_compressed() Elia Pinto
2026-10-31 11:39 ` [PATCH 3/4] index-pack: use size_t for object sizes Elia Pinto
2026-10-31 11:39 ` [PATCH 4/4] pack-check: use size_t for the size passed to verify callbacks Elia Pinto

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox