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

[PATCH 1/7] v0 protocol: fix infinite loop when parsing multi-valued capabilities

From
Jeff King <peff@peff.net>
Date
Apr 12, 2023, 06:29 UTC
Message-ID
<20230412062924.GA1681312@coredump.intra.peff.net>
In-Reply-To
<20230412062300.GA838367@coredump.intra.peff.net>

If Git's client-side parsing of an upload-pack response (so git-fetch or ls-remote) sees multiple instances of a single capability, it can enter an infinite loop due to a bug in advancing the "offset" parameter in the parser.

This bug can't happen between a client and server of the same Git version. The client bug is in parse_feature_value() when the caller passes in an offset parameter. And that only happens when the v0 protocol is parsing "symref" and "object-format" capabilities, via next_server_feature_value(). But Git has never produced multiple object-format capabilities, and it stopped producing multiple symref values in d007dbf7d6 (Revert "upload-pack: send non-HEAD symbolic refs", 2013-11-18).

However, upload-pack did produce multiple symref entries for a while, and they are valid. Plus other implementations, such as Dulwich will still do so. So we should handle them. And even if we do not expect it, it is obviously a bug for the parser to enter an infinite loop.

The bug itself is pretty simple. Commit 2c6a403d96 (connect: add function to parse multiple v1 capability values, 2020-05-25) added the "offset" parameter, which is used as both an in- and out-parameter. When parsing the first "symref" capability, *offset will be 0 on input, and after parsing the capability, we set *offset to an index just past the value by taking a pointer difference "(value + end) - feature_list".

But on the second call, now *offset is set to that larger index, which lets us skip past the first "symref" capability. However, we do so by incrementing feature_list. That means our pointer difference is now too small; it is counting from where we resumed parsing, not from the start of the original feature_list pointer. And because we incremented feature_list only inside our function, and not the caller, that increment is lost next time the function is called.

The simplest solution is to account for those skipped bytes by incrementing *offset, rather than assigning to it. (The other possible solution is to add an extra level of pointer indirection to feature_list so that the caller knows we moved it forward, but that seems more complicated).

Since the bug can't be reproduced using the current version of git-upload-pack, we'll instead hard-code an input which triggers the problem. Before this patch it loops forever re-parsing the second symref entry. Now we check both that it finishes, and that it parses both entries correctly (a case we could not test at all before).

We don't need to worry about testing v2 here; it communicates the capabilities in a completely different way, and doesn't use this code at all. There are tests earlier in t5512 that are meant to cover this (they don't, but we'll address that in a future patch).

Reported-by: Jonas Haag <jonas@lophus.org>
Signed-off-by: Jeff King <peff@peff.net>
---
 connect.c            |  4 ++--
 t/t5512-ls-remote.sh | 44 ++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 46 insertions(+), 2 deletions(-)
diff --git a/connect.c b/connect.c
index c0c8a38178..5e265ba9d7 100644
--- a/connect.c
+++ b/connect.c
@@ -616,7 +616,7 @@ const char *parse_feature_value(const char *feature_list, const char *feature, i
 				if (lenp)
 					*lenp = 0;
 				if (offset)
-					*offset = found + len - feature_list;
+					*offset += found + len - feature_list;
 				return value;
 			}
 			/* feature with a value (e.g., "agent=git/1.2.3") */
@@ -628,7 +628,7 @@ const char *parse_feature_value(const char *feature_list, const char *feature, i
 				if (lenp)
 					*lenp = end;
 				if (offset)
-					*offset = value + end - feature_list;
+					*offset += value + end - feature_list;
 				return value;
 			}
 			/*
diff --git a/t/t5512-ls-remote.sh b/t/t5512-ls-remote.sh
index 20d063fb9a..64dc6fa91c 100755
--- a/t/t5512-ls-remote.sh
+++ b/t/t5512-ls-remote.sh
@@ -15,6 +15,19 @@ generate_references () {
 	done
 }
 
+test_expect_success 'set up fake upload-pack' '
+	# This can be used to simulate an upload-pack that just shows the
+	# contents of the "input" file (prepared with the test-tool pkt-line
+	# helper), and does not do any negotiation (since ls-remote does not
+	# need it).
+	write_script cat-input <<-\EOF
+	# send our initial advertisement/response
+	cat input
+	# soak up the flush packet from the client
+	cat
+	EOF
+'
+
 test_expect_success 'dies when no remote found' '
 	test_must_fail git ls-remote
 '
@@ -360,4 +373,35 @@ test_expect_success 'ls-remote prefixes work with all protocol versions' '
 	test_cmp expect actual.v2
 '
 
+test_expect_success 'v0 clients can handle multiple symrefs' '
+	# Modern versions of Git will not return multiple symref capabilities
+	# for v0, so we have to hard-code the response. Note that we will
+	# always use both v0 and object-format=sha1 here, as the hard-coded
+	# response reflects a server that only supports those.
+	oid=1234567890123456789012345678901234567890 &&
+	symrefs="symref=refs/remotes/origin/HEAD:refs/remotes/origin/main" &&
+	symrefs="$symrefs symref=HEAD:refs/heads/main" &&
+
+	test-tool pkt-line pack >input.q <<-EOF &&
+	$oid HEADQ$symrefs
+	$oid refs/heads/main
+	$oid refs/remotes/origin/HEAD
+	$oid refs/remotes/origin/main
+	0000
+	EOF
+	q_to_nul <input.q >input &&
+
+	cat >expect <<-EOF &&
+	ref: refs/heads/main	HEAD
+	$oid	HEAD
+	$oid	refs/heads/main
+	ref: refs/remotes/origin/main	refs/remotes/origin/HEAD
+	$oid	refs/remotes/origin/HEAD
+	$oid	refs/remotes/origin/main
+	EOF
+
+	git ls-remote --symref --upload-pack=./cat-input . >actual &&
+	test_cmp expect actual
+'
+
 test_done
-- 
2.40.0.493.gfc602f1919
Previous: Jeff KingNext: Jeff King
Message 11 of 30 in “Infinite loop + memory leak in annotate_refs_with_symref_info”
  1. Jonas HaagApr 11, 2023
  2. Taylor BlauApr 11, 2023
  3. Taylor BlauApr 11, 2023
  4. brian m. carlsonApr 12, 2023
  5. Jeff KingApr 11, 2023
  6. Jeff KingApr 11, 2023
  7. Taylor BlauApr 11, 2023
  8. Jeff KingApr 11, 2023
  9. Junio C HamanoApr 11, 2023
  10. 0/7 v0 multiple-symref infinite loop fix and test cleanupJeff King, Apr 12, 2023
  11. 1/7 v0 protocol: fix infinite loop when parsing multi-valued capabilitiesJeff King, Apr 12, 2023
  12. Jeff KingApr 12, 2023
  13. 1/7 v0 protocol: fix infinite loop when parsing multi-valued capabilitiesJeff King, Apr 12, 2023
  14. Jeff KingApr 12, 2023
  15. 2/7 t5512: stop referring to "v1" protocolJeff King, Apr 12, 2023
  16. 3/7 t5512: stop using jgit for capabilities^{} testJeff King, Apr 12, 2023
  17. Jeff KingApr 12, 2023
  18. 0/7 v0 multiple-symref infinite loop fix and test cleanupJeff King, Apr 14, 2023
  19. 1/7 v0 protocol: fix infinite loop when parsing multi-valued capabilitiesJeff King, Apr 14, 2023
  20. 2/7 t5512: stop referring to "v1" protocolJeff King, Apr 14, 2023
  21. 3/7 v0 protocol: fix sha1/sha256 confusion for capabilities^{}Jeff King, Apr 14, 2023
  22. 5/7 t5512: allow any protocol version for filtered symref testJeff King, Apr 14, 2023
  23. 4/7 t5512: add v2 support for "ls-remote --symref" testJeff King, Apr 14, 2023
  24. 6/7 t5512: test "ls-remote --heads --symref" filtering with v0 and v2Jeff King, Apr 14, 2023
  25. 7/7 v0 protocol: use size_t for capability length/offsetJeff King, Apr 14, 2023
  26. Junio C HamanoApr 17, 2023
  27. 4/7 t5512: add v2 support for "ls-remote --symref" testJeff King, Apr 12, 2023
  28. 5/7 t5512: allow any protocol version for filtered symref testJeff King, Apr 12, 2023
  29. 6/7 t5512: test "ls-remote --heads --symref" filtering with v0 and v2Jeff King, Apr 12, 2023
  30. 7/7 v0 protocol: use size_t for capability length/offsetJeff King, Apr 12, 2023

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.