From 7f0e4f6ac28b7a9494f3affd1336244d4fb0fe38 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 15 Sep 2021 14:35:31 -0400 Subject: ls-refs: ignore very long ref-prefix counts Because each "ref-prefix" capability from the client comes in its own pkt-line, there's no limit to the number of them that a misbehaving client may send. We read them all into a strvec, which means the client can waste arbitrary amounts of our memory by just sending us "ref-prefix foo" over and over. One possible solution is to just drop the connection when the limit is reached. If we set it high enough, then only misbehaving or malicious clients would hit it. But "high enough" is vague, and it's unfriendly if we guess wrong and a legitimate client hits this. But we can do better. Since supporting the ref-prefix capability is optional anyway, the client has to further cull the response based on their own patterns. So we can simply ignore the patterns once we cross a certain threshold. Note that we have to ignore _all_ patterns, not just the ones past our limit (since otherwise we'd send too little data). The limit here is fairly arbitrary, and probably much higher than anyone would need in practice. It might be worth limiting it further, if only because we check it linearly (so with "m" local refs and "n" patterns, we do "m * n" string comparisons). But if we care about optimizing this, an even better solution may be a more advanced data structure anyway. I didn't bother making the limit configurable, since it's so high and since Git should behave correctly in either case. It wouldn't be too hard to do, but it makes both the code and documentation more complex. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- t/t5701-git-serve.sh | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) (limited to 't') diff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh index 930721f053..520672f842 100755 --- a/t/t5701-git-serve.sh +++ b/t/t5701-git-serve.sh @@ -158,6 +158,37 @@ test_expect_success 'refs/heads prefix' ' test_cmp expect actual ' +test_expect_success 'ignore very large set of prefixes' ' + # generate a large number of ref-prefixes that we expect + # to match nothing; the value here exceeds TOO_MANY_PREFIXES + # from ls-refs.c. + { + echo command=ls-refs && + echo object-format=$(test_oid algo) && + echo 0001 && + perl -le "print \"ref-prefix refs/heads/\$_\" for (1..65536)" && + echo 0000 + } | + test-tool pkt-line pack >in && + + # and then confirm that we see unmatched prefixes anyway (i.e., + # that the prefix was not applied). + cat >expect <<-EOF && + $(git rev-parse HEAD) HEAD + $(git rev-parse refs/heads/dev) refs/heads/dev + $(git rev-parse refs/heads/main) refs/heads/main + $(git rev-parse refs/heads/release) refs/heads/release + $(git rev-parse refs/tags/annotated-tag) refs/tags/annotated-tag + $(git rev-parse refs/tags/one) refs/tags/one + $(git rev-parse refs/tags/two) refs/tags/two + 0000 + EOF + + test-tool serve-v2 --stateless-rpc out && + test-tool pkt-line unpack actual && + test_cmp expect actual +' + test_expect_success 'peel parameter' ' test-tool pkt-line pack >in <<-EOF && command=ls-refs -- cgit v1.3-5-g9baa From 108c265f272d30ffaee423f7cc35885e9ac5d0e5 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 15 Sep 2021 14:36:33 -0400 Subject: serve: reject bogus v2 "command=ls-refs=foo" When we see a line from the client like "command=ls-refs", we parse everything after the equals sign as a capability, which we check against our capabilities table. If we don't recognize the command (e.g., "command=foo"), we'll reject it. But in parse_command(), we use the same get_capability() parser for parsing non-command lines. So if we see "command=ls-refs=foo", we will feed "ls-refs=foo" to get_capability(), which will say "OK, that's ls-refs, with value 'foo'". But then we simply ignore the value entirely. The client is violating the spec here, which says: command = PKT-LINE("command=" key LF) key = 1*(ALPHA | DIGIT | "-_") I.e., the key is not even allowed to have an equals sign in it. Whereas a real non-command capability does allow a value: capability = PKT-LINE(key[=value] LF) So by reusing the same get_capability() parser, we are mixing up the "key" and "capability" tokens. However, since that parser tells us whether it saw an "=", we can still use it; we just need to reject any input that produces a non-NULL value field. The current behavior isn't really hurting anything (the client should never send such a request, and if it does, we just ignore the "value" part). But since it does violate the spec, let's tighten it up to prevent any surprising behavior. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- serve.c | 2 +- t/t5701-git-serve.sh | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) (limited to 't') diff --git a/serve.c b/serve.c index 1a7c8a118f..db5ecfed2d 100644 --- a/serve.c +++ b/serve.c @@ -220,7 +220,7 @@ static int parse_command(const char *key, struct protocol_capability **command) if (*command) die("command '%s' requested after already requesting command '%s'", out, (*command)->name); - if (!cmd || !cmd->advertise(the_repository, NULL) || !cmd->command) + if (!cmd || !cmd->advertise(the_repository, NULL) || !cmd->command || value) die("invalid command '%s'", out); *command = cmd; diff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh index 520672f842..2e51886def 100755 --- a/t/t5701-git-serve.sh +++ b/t/t5701-git-serve.sh @@ -72,6 +72,16 @@ test_expect_success 'request invalid command' ' test_i18ngrep "invalid command" err ' +test_expect_success 'requested command is command=value' ' + test-tool pkt-line pack >in <<-EOF && + command=ls-refs=whatever + object-format=$(test_oid algo) + 0000 + EOF + test_must_fail test-tool serve-v2 --stateless-rpc 2>err in <<-EOF && command=fetch -- cgit v1.3-5-g9baa From 0ab7eeccd9aea668819288c086dcdf57ca14a026 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 15 Sep 2021 14:36:36 -0400 Subject: serve: reject commands used as capabilities Our table of v2 "capabilities" contains everything we might tell the client we support. But there are differences in how we expect the client to respond. Some of the entries are true capabilities (i.e., we expect the client to say "yes, I support this"), and some are ones we expect them to send as commands (with "command=ls-refs" or similar). When we receive a capability used as a command, we complain about that. But when we receive a command used as a capability (e.g., just "ls-refs" in a pkt-line by itself), we silently ignore it. This isn't really hurting anything (clients shouldn't send it, and we'll ignore it), but we can tighten up the protocol to match what we expect to happen. There are two new tests here. The first one checks a capability used as a command, which already passes. The second tests a command as a capability, which this patch fixes. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- serve.c | 2 +- t/t5701-git-serve.sh | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) (limited to 't') diff --git a/serve.c b/serve.c index db5ecfed2d..b3fe9b5126 100644 --- a/serve.c +++ b/serve.c @@ -201,7 +201,7 @@ static int receive_client_capability(const char *key) const char *value; const struct protocol_capability *c = get_capability(key, &value); - if (!c || !c->advertise(the_repository, NULL)) + if (!c || c->command || !c->advertise(the_repository, NULL)) return 0; if (c->receive) diff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh index 2e51886def..3928424e1b 100755 --- a/t/t5701-git-serve.sh +++ b/t/t5701-git-serve.sh @@ -72,6 +72,27 @@ test_expect_success 'request invalid command' ' test_i18ngrep "invalid command" err ' +test_expect_success 'request capability as command' ' + test-tool pkt-line pack >in <<-EOF && + command=agent + object-format=$(test_oid algo) + 0000 + EOF + test_must_fail test-tool serve-v2 --stateless-rpc 2>err in <<-EOF && + command=ls-refs + object-format=$(test_oid algo) + fetch + 0000 + EOF + test_must_fail test-tool serve-v2 --stateless-rpc 2>err in <<-EOF && command=ls-refs=whatever -- cgit v1.3-5-g9baa From ccf094788c50c597972ee1fd9c2b554cadc0f14c Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 15 Sep 2021 14:36:38 -0400 Subject: ls-refs: reject unknown arguments The v2 ls-refs command may receive extra arguments from the client, one per pkt-line. The spec is pretty clear that the arguments must come from a specified set, but we silently ignore any unknown entries. For a well-behaved client this doesn't matter, but it makes testing and debugging more confusing. Let's tighten this up to match the spec. In theory this liberal behavior _could_ be useful for extending the protocol. But: - every other part of the protocol requires that the server first indicate that it supports the argument; this includes the fetch and object-info commands, plus the "unborn" capability added to ls-refs itself - it's not a very good extension mechanism anyway; without the server advertising support, clients would have no idea if the argument was silently ignored, or accepted and simply had no effect So we're not really losing anything by tightening this. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- ls-refs.c | 2 ++ t/t5701-git-serve.sh | 13 +++++++++++++ 2 files changed, 15 insertions(+) (limited to 't') diff --git a/ls-refs.c b/ls-refs.c index 07429f3a1c..883c0c58b2 100644 --- a/ls-refs.c +++ b/ls-refs.c @@ -170,6 +170,8 @@ int ls_refs(struct repository *r, struct packet_reader *request) } else if (!strcmp("unborn", arg)) data.unborn = allow_unborn; + else + die(_("unexpected line: '%s'"), arg); } if (request->status != PACKET_READ_FLUSH) diff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh index 3928424e1b..aa1827d841 100755 --- a/t/t5701-git-serve.sh +++ b/t/t5701-git-serve.sh @@ -147,6 +147,19 @@ test_expect_success 'basics of ls-refs' ' test_cmp expect actual ' +test_expect_success 'ls-refs complains about unknown options' ' + test-tool pkt-line pack >in <<-EOF && + command=ls-refs + object-format=$(test_oid algo) + 0001 + no-such-arg + 0000 + EOF + + test_must_fail test-tool serve-v2 --stateless-rpc 2>err in <<-EOF && command=ls-refs -- cgit v1.3-5-g9baa