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

[PATCHv3 7/9] send-pack/receive-pack: Allow server to refuse pushes with too many objects

From
Johan Herland <johan@herland.net>
Date
May 15, 2011, 21:37 UTC
Message-ID
<1305495440-30836-8-git-send-email-johan@herland.net>
In-Reply-To
<1305495440-30836-1-git-send-email-johan@herland.net>

Add a new receive.objectCountLimit config variable which defines an upper limit on the number of objects to accept in a single push.

This limit is advertised to clients, using the new "limit-object-count=<num>" capability. The client side - aka. send-pack - parses this capability and forwards it to pack-objects, using the recently added --max-object-count option. pack-objects then checks the generated pack against the limit and aborts the pack generation if the pack would have too many objects.

Additionally - for older clients that do not understand the capability - the server aborts the transfer if the number of objects in the transferred pack exceeds the limit. This is a suboptimal fallback solution, as it leaves the client with a broken pipe, and likely a confused user.

Server administrators might want to use this config variable to prevent unintended large pushes from entering the repo (typically a result of the user not being aware of exactly what is being pushed, e.g. pushing a large rewritten history). Note that this config variable is not intended to protect against DoS attacks, since there are countless other ways to attempt to DoS a server without violating this limit.

Traditionally, this kind of limit would be imposed by a pre-receive or update hook, but both of those run _after_ the pack has been received and stored by receive-pack, so they cannot prevent the pack from being stored on the server.

Documentation and tests are included.
Signed-off-by: Johan Herland <johan@herland.net>
---
 Documentation/config.txt                          |    9 +++
 Documentation/technical/protocol-capabilities.txt |    2 +
 builtin/receive-pack.c                            |   13 ++++-
 builtin/send-pack.c                               |   10 +++
 send-pack.h                                       |    1 +
 t/t5400-send-pack.sh                              |   66 +++++++++++++++++++++
 6 files changed, 100 insertions(+), 1 deletions(-)
diff --git a/Documentation/config.txt b/Documentation/config.txt
index 056e01f..fd6c130 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -1599,6 +1599,15 @@ receive.unpackLimit::
 	especially on slow filesystems.  If not set, the value of
 	`transfer.unpackLimit` is used instead.
 
+receive.objectCountLimit::
+	If the number of objects received in a push exceeds this limit,
+	then the entire push will be refused. This is meant to prevent
+	an unintended large push (typically a result of the user not
+	being aware of exactly what is being pushed, e.g. pushing a
+	large rewritten history) from entering the repo. If not set,
+	there is no upper limit on the number of objects transferred
+	in a single push.
+
 receive.denyDeletes::
 	If set to true, git-receive-pack will deny a ref update that deletes
 	the ref. Use this to prevent such a ref deletion via a push.
diff --git a/Documentation/technical/protocol-capabilities.txt b/Documentation/technical/protocol-capabilities.txt
index 11849a3..aac66af 100644
--- a/Documentation/technical/protocol-capabilities.txt
+++ b/Documentation/technical/protocol-capabilities.txt
@@ -205,5 +205,7 @@ the server. If the check fails, the client must abort the upload, and
 report the reason for the aborted push back to the user.
 The following "limit-*" capabilites are recognized:
 
+ - limit-object-count=<num> (Maximum number of objects in a pack)
+
 More "limit-*" capabilities may be added in the future. The client
 is free to ignore any "limit-*" capabilities it does not understand.
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index c55989d..d919e17 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -28,6 +28,7 @@ static int receive_fsck_objects;
 static int receive_unpack_limit = -1;
 static int transfer_unpack_limit = -1;
 static int unpack_limit = 100;
+static unsigned long limit_object_count;
 static int report_status;
 static int use_sideband;
 static int prefer_ofs_delta = 1;
@@ -73,6 +74,11 @@ static int receive_pack_config(const char *var, const char *value, void *cb)
 		return 0;
 	}
 
+	if (strcmp(var, "receive.objectcountlimit") == 0) {
+		limit_object_count = git_config_ulong(var, value);
+		return 0;
+	}
+
 	if (strcmp(var, "receive.fsckobjects") == 0) {
 		receive_fsck_objects = git_config_bool(var, value);
 		return 0;
@@ -112,6 +118,9 @@ static const char *capabilities()
 	int ret = snprintf(buf, sizeof(buf),
 			   " report-status delete-refs side-band-64k%s",
 			   prefer_ofs_delta ? " ofs-delta" : "");
+	if (limit_object_count > 0)
+		ret += snprintf(buf + ret, sizeof(buf) - ret,
+				" limit-object-count=%lu", limit_object_count);
 	assert(ret < sizeof(buf));
 	return buf;
 }
@@ -656,7 +665,9 @@ static const char *unpack(void)
 			"--pack_header=%"PRIu32",%"PRIu32,
 			ntohl(hdr.hdr_version), ntohl(hdr.hdr_entries));
 
-	if (ntohl(hdr.hdr_entries) < unpack_limit) {
+	if (limit_object_count > 0 && ntohl(hdr.hdr_entries) > limit_object_count)
+		return "received pack exceeds configured receive.objectCountLimit";
+	else if (ntohl(hdr.hdr_entries) < unpack_limit) {
 		int code, i = 0;
 		const char *unpacker[4];
 		unpacker[i++] = "unpack-objects";
diff --git a/builtin/send-pack.c b/builtin/send-pack.c
index f571917..4103116 100644
--- a/builtin/send-pack.c
+++ b/builtin/send-pack.c
@@ -49,9 +49,11 @@ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *ext
 		NULL,
 		NULL,
 		NULL,
+		NULL,
 	};
 	struct child_process po;
 	int i;
+	char buf[40];
 
 	i = 4;
 	if (args->use_thin_pack)
@@ -62,6 +64,11 @@ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *ext
 		argv[i++] = "-q";
 	if (args->progress)
 		argv[i++] = "--progress";
+	if (args->max_object_count > 0) {
+		snprintf(buf, sizeof(buf), "--max-object-count=%lu",
+			 args->max_object_count);
+		argv[i++] = buf;
+	}
 	memset(&po, 0, sizeof(po));
 	po.argv = argv;
 	po.in = -1;
@@ -253,6 +260,7 @@ int send_pack(struct send_pack_args *args,
 	unsigned cmds_sent = 0;
 	int ret = 0;
 	struct async demux;
+	const char *p;
 
 	/* Does the other end support the reporting? */
 	if (server_supports("report-status"))
@@ -263,6 +271,8 @@ int send_pack(struct send_pack_args *args,
 		args->use_ofs_delta = 1;
 	if (server_supports("side-band-64k"))
 		use_sideband = 1;
+	if ((p = server_supports("limit-object-count=")))
+		args->max_object_count = strtoul(p + 19, NULL, 10);
 
 	if (!remote_refs) {
 		fprintf(stderr, "No refs in common and none specified; doing nothing.\n"
diff --git a/send-pack.h b/send-pack.h
index 05d7ab1..eaacb20 100644
--- a/send-pack.h
+++ b/send-pack.h
@@ -12,6 +12,7 @@ struct send_pack_args {
 		use_ofs_delta:1,
 		dry_run:1,
 		stateless_rpc:1;
+	unsigned long max_object_count;
 };
 
 int send_pack(struct send_pack_args *args,
diff --git a/t/t5400-send-pack.sh b/t/t5400-send-pack.sh
index 0eace37..f1f8f80 100755
--- a/t/t5400-send-pack.sh
+++ b/t/t5400-send-pack.sh
@@ -222,4 +222,70 @@ test_expect_success 'deny pushing to delete current branch' '
 	)
 '
 
+echo "0000" > pkt-flush
+
+test_expect_success 'verify that limit-object-count capability is not advertised by default' '
+	rewound_push_setup &&
+	(
+	    cd parent &&
+	    test_might_fail git receive-pack . <../pkt-flush >output &&
+	    test_must_fail grep -q "limit-object-count" output
+	)
+'
+
+test_expect_success 'verify that receive.objectCountLimit triggers limit-object-count capability' '
+	(
+	    cd parent &&
+	    git config receive.objectCountLimit 1 &&
+	    test_might_fail git receive-pack . <../pkt-flush >output &&
+	    grep -q "limit-object-count=1" output
+	)
+'
+
+test_expect_success 'deny pushing when receive.objectCountLimit is exceeded' '
+	(
+	    cd child &&
+	    git reset --hard origin/master &&
+	    echo three > file && git commit -a -m three &&
+	    test_must_fail git send-pack ../parent master 2>errs &&
+	    grep -q "object count limit" errs
+	) &&
+	parent_head=$(cd parent && git rev-parse --verify master) &&
+	child_head=$(cd child && git rev-parse --verify master) &&
+	test "$parent_head" != "$child_head"
+'
+
+test_expect_success 'repeated push failure proves that objects were not stored remotely' '
+	(
+	    cd child &&
+	    test_must_fail git send-pack ../parent master 2>errs &&
+	    grep -q "object count limit" errs
+	) &&
+	parent_head=$(cd parent && git rev-parse --verify master) &&
+	child_head=$(cd child && git rev-parse --verify master) &&
+	test "$parent_head" != "$child_head"
+'
+
+test_expect_success 'increase receive.objectCountLimit' '
+	(
+	    cd parent &&
+	    git config receive.objectCountLimit 10 &&
+	    test_might_fail git receive-pack . <../pkt-flush >output &&
+	    grep -q "limit-object-count=10" output
+	)
+'
+
+test_expect_success 'push is allowed when object limit is not exceeded' '
+	(
+	    cd child &&
+	    git send-pack ../parent master 2>errs &&
+	    test_must_fail grep -q "object count limit" errs &&
+	    # Also no error message from remote receive-pack
+	    test_must_fail grep -q "receive\\.objectCountLimit" errs
+	) &&
+	parent_head=$(cd parent && git rev-parse --verify master) &&
+	child_head=$(cd child && git rev-parse --verify master) &&
+	test "$parent_head" = "$child_head"
+'
+
 test_done
-- 
1.7.5.rc1.3.g4d7b
Previous: Sverre RabbelierNext: Johan Herland
Message 46 of 53 in “receive-pack: Add receive.denyObjectLimit to refuse push with too many objects”
  1. 2/2 receive-pack: Add receive.denyObjectLimit to refuse push with too many objectsJohan Herland, May 13, 2011
  2. Junio C HamanoMay 13, 2011
  3. Johan HerlandMay 14, 2011
  4. 2/2 receive-pack: Add receive.objectCountLimit to refuse push with too many objectsJohan Herland, May 14, 2011
  5. Shawn PearceMay 14, 2011
  6. Johan HerlandMay 14, 2011
  7. Shawn PearceMay 14, 2011
  8. Johan HerlandMay 15, 2011
  9. 0/9 Push limitsJohan Herland, May 15, 2011
  10. 1/9 Update technical docs to reflect side-band-64k capability in receive-packJohan Herland, May 15, 2011
  11. 2/9 send-pack: Attempt to retrieve remote status even if pack-objects failsJohan Herland, May 15, 2011
  12. Jeff KingMay 16, 2011
  13. Jeff KingMay 16, 2011
  14. Jeff KingMay 16, 2011
  15. 1/3 connect: treat generic proxy processes like ssh processesJeff King, May 16, 2011
  16. Johannes SixtMay 16, 2011
  17. Junio C HamanoMay 16, 2011
  18. Jeff KingMay 17, 2011
  19. Johannes SixtMay 17, 2011
  20. Jeff KingMay 18, 2011
  21. 2/3 connect: let callers know if connection is a socketJeff King, May 16, 2011
  22. 3/3 send-pack: avoid deadlock on git:// push with failed pack-objectsJeff King, May 16, 2011
  23. Johannes SixtMay 16, 2011
  24. Jeff KingMay 17, 2011
  25. Windows: add a wrapper for the shutdown() system callJohannes Sixt, May 18, 2011
  26. 3/9 pack-objects: Allow --max-pack-size to be used together with --stdoutJohan Herland, May 15, 2011
  27. Shawn PearceMay 15, 2011
  28. Johan HerlandMay 16, 2011
  29. Junio C HamanoMay 16, 2011
  30. Johan HerlandMay 16, 2011
  31. 4/9 pack-objects: Teach new option --max-object-count, similar to --max-pack-sizeJohan Herland, May 15, 2011
  32. Shawn PearceMay 15, 2011
  33. Johan HerlandMay 15, 2011
  34. Shawn PearceMay 15, 2011
  35. Junio C HamanoMay 16, 2011
  36. Johan HerlandMay 16, 2011
  37. 5/9 pack-objects: Teach new option --max-commit-count, limiting #commits in packJohan Herland, May 15, 2011
  38. 6/9 receive-pack: Prepare for addition of the new 'limit-*' family of capabilitiesJohan Herland, May 15, 2011
  39. Junio C HamanoMay 16, 2011
  40. Johan HerlandMay 16, 2011
  41. Sverre RabbelierMay 16, 2011
  42. Junio C HamanoMay 16, 2011
  43. Sverre RabbelierMay 16, 2011
  44. Junio C HamanoMay 16, 2011
  45. Sverre RabbelierMay 16, 2011
  46. 7/9 send-pack/receive-pack: Allow server to refuse pushes with too many objectsJohan Herland, May 15, 2011
  47. 8/9 send-pack/receive-pack: Allow server to refuse pushing too large packsJohan Herland, May 15, 2011
  48. 9/9 send-pack/receive-pack: Allow server to refuse pushes with too many commitsJohan Herland, May 15, 2011
  49. Ævar Arnfjörð BjarmasonMay 15, 2011
  50. Junio C HamanoMay 14, 2011
  51. Shawn PearceMay 14, 2011
  52. Johannes SixtMay 13, 2011
  53. Johan HerlandMay 14, 2011

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.