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>
This commit is contained in:

committed by
Junio C Hamano

parent
0181b600a6
commit
b0df0c16ea
13
fetch-pack.c
13
fetch-pack.c
@ -1451,6 +1451,13 @@ enum fetch_state {
|
||||
FETCH_DONE,
|
||||
};
|
||||
|
||||
static void do_check_stateless_delimiter(const struct fetch_pack_args *args,
|
||||
struct packet_reader *reader)
|
||||
{
|
||||
check_stateless_delimiter(args->stateless_rpc, reader,
|
||||
_("git fetch-pack: expected response end packet"));
|
||||
}
|
||||
|
||||
static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
|
||||
int fd[2],
|
||||
const struct ref *orig_ref,
|
||||
@ -1535,6 +1542,10 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
|
||||
/* Process ACKs/NAKs */
|
||||
switch (process_acks(negotiator, &reader, &common)) {
|
||||
case READY:
|
||||
/*
|
||||
* Don't check for response delimiter; get_pack() will
|
||||
* read the rest of this response.
|
||||
*/
|
||||
state = FETCH_GET_PACK;
|
||||
break;
|
||||
case COMMON_FOUND:
|
||||
@ -1542,6 +1553,7 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
|
||||
seen_ack = 1;
|
||||
/* fallthrough */
|
||||
case NO_COMMON_FOUND:
|
||||
do_check_stateless_delimiter(args, &reader);
|
||||
state = FETCH_SEND_REQUEST;
|
||||
break;
|
||||
}
|
||||
@ -1561,6 +1573,7 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
|
||||
process_section_header(&reader, "packfile", 0);
|
||||
if (get_pack(args, fd, pack_lockfile, sought, nr_sought))
|
||||
die(_("git fetch-pack: fetch failed."));
|
||||
do_check_stateless_delimiter(args, &reader);
|
||||
|
||||
state = FETCH_DONE;
|
||||
break;
|
||||
|
Reference in New Issue
Block a user