Skip to content

Commit 7f0e4f6

Browse files
peffgitster
authored andcommitted
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 <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent f0a35c9 commit 7f0e4f6

File tree

2 files changed

+49
-2
lines changed

2 files changed

+49
-2
lines changed

ls-refs.c

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ static void ensure_config_read(void)
4040
config_read = 1;
4141
}
4242

43+
/*
44+
* If we see this many or more "ref-prefix" lines from the client, we consider
45+
* it "too many" and will avoid using the prefix feature entirely.
46+
*/
47+
#define TOO_MANY_PREFIXES 65536
48+
4349
/*
4450
* Check if one of the prefixes is a prefix of the ref.
4551
* If no prefixes were provided, all refs match.
@@ -158,15 +164,25 @@ int ls_refs(struct repository *r, struct packet_reader *request)
158164
data.peel = 1;
159165
else if (!strcmp("symrefs", arg))
160166
data.symrefs = 1;
161-
else if (skip_prefix(arg, "ref-prefix ", &out))
162-
strvec_push(&data.prefixes, out);
167+
else if (skip_prefix(arg, "ref-prefix ", &out)) {
168+
if (data.prefixes.nr < TOO_MANY_PREFIXES)
169+
strvec_push(&data.prefixes, out);
170+
}
163171
else if (!strcmp("unborn", arg))
164172
data.unborn = allow_unborn;
165173
}
166174

167175
if (request->status != PACKET_READ_FLUSH)
168176
die(_("expected flush after ls-refs arguments"));
169177

178+
/*
179+
* If we saw too many prefixes, we must avoid using them at all; as
180+
* soon as we have any prefix, they are meant to form a comprehensive
181+
* list.
182+
*/
183+
if (data.prefixes.nr >= TOO_MANY_PREFIXES)
184+
strvec_clear(&data.prefixes);
185+
170186
send_possibly_unborn_head(&data);
171187
if (!data.prefixes.nr)
172188
strvec_push(&data.prefixes, "");

t/t5701-git-serve.sh

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,37 @@ test_expect_success 'refs/heads prefix' '
158158
test_cmp expect actual
159159
'
160160

161+
test_expect_success 'ignore very large set of prefixes' '
162+
# generate a large number of ref-prefixes that we expect
163+
# to match nothing; the value here exceeds TOO_MANY_PREFIXES
164+
# from ls-refs.c.
165+
{
166+
echo command=ls-refs &&
167+
echo object-format=$(test_oid algo) &&
168+
echo 0001 &&
169+
perl -le "print \"ref-prefix refs/heads/\$_\" for (1..65536)" &&
170+
echo 0000
171+
} |
172+
test-tool pkt-line pack >in &&
173+
174+
# and then confirm that we see unmatched prefixes anyway (i.e.,
175+
# that the prefix was not applied).
176+
cat >expect <<-EOF &&
177+
$(git rev-parse HEAD) HEAD
178+
$(git rev-parse refs/heads/dev) refs/heads/dev
179+
$(git rev-parse refs/heads/main) refs/heads/main
180+
$(git rev-parse refs/heads/release) refs/heads/release
181+
$(git rev-parse refs/tags/annotated-tag) refs/tags/annotated-tag
182+
$(git rev-parse refs/tags/one) refs/tags/one
183+
$(git rev-parse refs/tags/two) refs/tags/two
184+
0000
185+
EOF
186+
187+
test-tool serve-v2 --stateless-rpc <in >out &&
188+
test-tool pkt-line unpack <out >actual &&
189+
test_cmp expect actual
190+
'
191+
161192
test_expect_success 'peel parameter' '
162193
test-tool pkt-line pack >in <<-EOF &&
163194
command=ls-refs

0 commit comments

Comments
 (0)