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 --- ls-refs.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) (limited to 'ls-refs.c') diff --git a/ls-refs.c b/ls-refs.c index df7f167433..07429f3a1c 100644 --- a/ls-refs.c +++ b/ls-refs.c @@ -40,6 +40,12 @@ static void ensure_config_read(void) config_read = 1; } +/* + * If we see this many or more "ref-prefix" lines from the client, we consider + * it "too many" and will avoid using the prefix feature entirely. + */ +#define TOO_MANY_PREFIXES 65536 + /* * Check if one of the prefixes is a prefix of the ref. * If no prefixes were provided, all refs match. @@ -158,8 +164,10 @@ int ls_refs(struct repository *r, struct packet_reader *request) data.peel = 1; else if (!strcmp("symrefs", arg)) data.symrefs = 1; - else if (skip_prefix(arg, "ref-prefix ", &out)) - strvec_push(&data.prefixes, out); + else if (skip_prefix(arg, "ref-prefix ", &out)) { + if (data.prefixes.nr < TOO_MANY_PREFIXES) + strvec_push(&data.prefixes, out); + } else if (!strcmp("unborn", arg)) data.unborn = allow_unborn; } @@ -167,6 +175,14 @@ int ls_refs(struct repository *r, struct packet_reader *request) if (request->status != PACKET_READ_FLUSH) die(_("expected flush after ls-refs arguments")); + /* + * If we saw too many prefixes, we must avoid using them at all; as + * soon as we have any prefix, they are meant to form a comprehensive + * list. + */ + if (data.prefixes.nr >= TOO_MANY_PREFIXES) + strvec_clear(&data.prefixes); + send_possibly_unborn_head(&data); if (!data.prefixes.nr) strvec_push(&data.prefixes, ""); -- cgit v1.2.3 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 ++ 1 file changed, 2 insertions(+) (limited to 'ls-refs.c') 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) -- cgit v1.2.3