@matheusbsbs / matheus-bagliano / commits / b0df0c16ea
Fork 0

stateless-connect: send response end packet

Currently, remote-curl acts as a proxy and blindly forwards packets between an HTTP server and fetch-pack. In the case of a stateless RPC connection where the connection is terminated before the transaction is complete, remote-curl will blindly forward the packets before waiting on more input from fetch-pack. Meanwhile, fetch-pack will read the transaction and continue reading, expecting more input to continue the transaction. This results in a deadlock between the two processes. This can be seen in the following command which does not terminate: $ git -c protocol.version=2 clone https://github.com/git/git.git --shallow-since=20151012 Cloning into 'git'... whereas the v1 version does terminate as expected: $ git -c protocol.version=1 clone https://github.com/git/git.git --shallow-since=20151012 Cloning into 'git'... fatal: the remote end hung up unexpectedly Instead of blindly forwarding packets, make remote-curl insert a response end packet after proxying the responses from the remote server when using stateless_connect(). On the RPC client side, ensure that each response ends as described. A separate control packet is chosen because we need to be able to differentiate between what the remote server sends and remote-curl's control packets. By ensuring in the remote-curl code that a server cannot send response end packets, we prevent a malicious server from being able to perform a denial of service attack in which they spoof a response end packet and cause the described deadlock to happen. Reported-by: Force Charlie <charlieio@outlook.com> Helped-by: Jeff King <peff@peff.net> Signed-off-by: Denton Liu <liu.denton@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Denton Liu committed May 19, 2020 at 06:54 UTC b0df0c16ead4c5512d506dcbbdf31194d992803c
10 files changed +60 -5
Documentation/gitremote-helpers.txt
+3 -1
@@ -405,7 +405,9 @@ Supported if the helper has the "connect" capability.
405 trying to fall back). After line feed terminating the positive
406 (empty) response, the output of the service starts. Messages
407 (both request and response) must consist of zero or more
408 - PKT-LINEs, terminating in a flush packet. The client must not
408 + PKT-LINEs, terminating in a flush packet. Response messages will
409 + then have a response end packet after the flush packet to
410 + indicate the end of a response. The client must not
411 expect the server to store any state in between request-response
412 pairs. After the connection ends, the remote helper exits.
413 +
Documentation/technical/protocol-v2.txt
+2
@@ -33,6 +33,8 @@ In protocol v2 these special packets will have the following semantics:
33
34 * '0000' Flush Packet (flush-pkt) - indicates the end of a message
35 * '0001' Delimiter Packet (delim-pkt) - separates sections of a message
36 + * '0002' Message Packet (response-end-pkt) - indicates the end of a response
37 + for stateless connections
38
39 Initial Client Request
40 ----------------------
builtin/fetch-pack.c
+1 -1
@@ -224,7 +224,7 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
224 version = discover_version(&reader);
225 switch (version) {
226 case protocol_v2:
227 - get_remote_refs(fd[1], &reader, &ref, 0, NULL, NULL);
227 + get_remote_refs(fd[1], &reader, &ref, 0, NULL, NULL, args.stateless_rpc);
228 break;
229 case protocol_v1:
230 case protocol_v0:
connect.c
+15 -1
@@ -406,10 +406,21 @@ out:
406 return ret;
407 }
408
409 +void check_stateless_delimiter(int stateless_rpc,
410 + struct packet_reader *reader,
411 + const char *error)
412 +{
413 + if (!stateless_rpc)
414 + return; /* not in stateless mode, no delimiter expected */
415 + if (packet_reader_read(reader) != PACKET_READ_RESPONSE_END)
416 + die("%s", error);
417 +}
418 +
419 struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,
420 struct ref **list, int for_push,
421 const struct argv_array *ref_prefixes,
412 - const struct string_list *server_options)
422 + const struct string_list *server_options,
423 + int stateless_rpc)
424 {
425 int i;
426 *list = NULL;
@@ -446,6 +457,9 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,
457 if (reader->status != PACKET_READ_FLUSH)
458 die(_("expected flush after ref listing"));
459
460 + check_stateless_delimiter(stateless_rpc, reader,
461 + _("expected response end packet after ref listing"));
462 +
463 return list;
464 }
465
connect.h
+4
@@ -22,4 +22,8 @@ int server_supports_v2(const char *c, int die_on_error);
22 int server_supports_feature(const char *c, const char *feature,
23 int die_on_error);
24
25 +void check_stateless_delimiter(int stateless_rpc,
26 + struct packet_reader *reader,
27 + const char *error);
28 +
29 #endif
fetch-pack.c
+13
@@ -1451,6 +1451,13 @@ enum fetch_state {
1451 FETCH_DONE,
1452 };
1453
1454 +static void do_check_stateless_delimiter(const struct fetch_pack_args *args,
1455 + struct packet_reader *reader)
1456 +{
1457 + check_stateless_delimiter(args->stateless_rpc, reader,
1458 + _("git fetch-pack: expected response end packet"));
1459 +}
1460 +
1461 static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
1462 int fd[2],
1463 const struct ref *orig_ref,
@@ -1535,6 +1542,10 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
1542 /* Process ACKs/NAKs */
1543 switch (process_acks(negotiator, &reader, &common)) {
1544 case READY:
1545 + /*
1546 + * Don't check for response delimiter; get_pack() will
1547 + * read the rest of this response.
1548 + */
1549 state = FETCH_GET_PACK;
1550 break;
1551 case COMMON_FOUND:
@@ -1542,6 +1553,7 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
1553 seen_ack = 1;
1554 /* fallthrough */
1555 case NO_COMMON_FOUND:
1556 + do_check_stateless_delimiter(args, &reader);
1557 state = FETCH_SEND_REQUEST;
1558 break;
1559 }
@@ -1561,6 +1573,7 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
1573 process_section_header(&reader, "packfile", 0);
1574 if (get_pack(args, fd, pack_lockfile, sought, nr_sought))
1575 die(_("git fetch-pack: fetch failed."));
1576 + do_check_stateless_delimiter(args, &reader);
1577
1578 state = FETCH_DONE;
1579 break;
remote-curl.c
+5
@@ -703,6 +703,8 @@ static void check_pktline(struct check_pktline_state *state, const char *ptr, si
703 state->remaining = packet_length(state->len_buf);
704 if (state->remaining < 0) {
705 die(_("remote-curl: bad line length character: %.4s"), state->len_buf);
706 + } else if (state->remaining == 2) {
707 + die(_("remote-curl: unexpected response end packet"));
708 } else if (state->remaining < 4) {
709 state->remaining = 0;
710 } else {
@@ -991,6 +993,9 @@ retry:
993 if (rpc_in_data.pktline_state.remaining)
994 err = error(_("%d bytes of body are still expected"), rpc_in_data.pktline_state.remaining);
995
996 + if (stateless_connect)
997 + packet_response_end(rpc->in);
998 +
999 curl_slist_free_all(headers);
1000 free(gzip_body);
1001 return err;
remote.h
+2 -1
@@ -179,7 +179,8 @@ struct ref **get_remote_heads(struct packet_reader *reader,
179 struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,
180 struct ref **list, int for_push,
181 const struct argv_array *ref_prefixes,
182 - const struct string_list *server_options);
182 + const struct string_list *server_options,
183 + int stateless_rpc);
184
185 int resolve_remote_symref(struct ref *ref, struct ref *list);
186
t/t5702-protocol-v2.sh
+13
@@ -620,6 +620,19 @@ test_expect_success 'clone repository with http:// using protocol v2 with incomp
620 test_i18ngrep "bytes of body are still expected" err
621 '
622
623 +test_expect_success 'clone with http:// using protocol v2 and invalid parameters' '
624 + test_when_finished "rm -f log" &&
625 +
626 + test_must_fail env GIT_TRACE_PACKET="$(pwd)/log" GIT_TRACE_CURL="$(pwd)/log" \
627 + git -c protocol.version=2 \
628 + clone --shallow-since=20151012 "$HTTPD_URL/smart/http_parent" http_child_invalid &&
629 +
630 + # Client requested to use protocol v2
631 + grep "Git-Protocol: version=2" log &&
632 + # Server responded using protocol v2
633 + grep "git< version 2" log
634 +'
635 +
636 test_expect_success 'clone big repository with http:// using protocol v2' '
637 test_when_finished "rm -f log" &&
638
transport.c
+2 -1
@@ -297,7 +297,8 @@ static struct ref *handshake(struct transport *transport, int for_push,
297 if (must_list_refs)
298 get_remote_refs(data->fd[1], &reader, &refs, for_push,
299 ref_prefixes,
300 - transport->server_options);
300 + transport->server_options,
301 + transport->stateless_rpc);
302 break;
303 case protocol_v1:
304 case protocol_v0: