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 with a partially written packet, remote-curl will blindly send the partially written packet before waiting on more input from fetch-pack. Meanwhile, fetch-pack will read the partial packet and continue reading, expecting more input. This results in a deadlock between the two processes. Instead of blindly forwarding packets, inspect each packet to ensure that it is a full packet, erroring out if a partial packet is sent. Helped-by: Jeff King <peff@xxxxxxxx> Signed-off-by: Denton Liu <liu.denton@xxxxxxxxx> --- Notes: Unfortunately, I'm not really sure how to test this. remote-curl.c | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/remote-curl.c b/remote-curl.c index da3e07184a..8b740354e5 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -682,6 +682,8 @@ static curlioerr rpc_ioctl(CURL *handle, int cmd, void *clientp) struct rpc_in_data { struct rpc_state *rpc; struct active_request_slot *slot; + struct strbuf len_buf; + int remaining; }; /* @@ -692,6 +694,7 @@ static size_t rpc_in(char *ptr, size_t eltsize, size_t nmemb, void *buffer_) { size_t size = eltsize * nmemb; + size_t unwritten = size; struct rpc_in_data *data = buffer_; long response_code; @@ -702,7 +705,42 @@ static size_t rpc_in(char *ptr, size_t eltsize, return size; if (size) data->rpc->any_written = 1; - write_or_die(data->rpc->in, ptr, size); + + while (unwritten) { + if (!data->remaining) { + int digits_remaining = 4 - data->len_buf.len; + if (digits_remaining > unwritten) + digits_remaining = unwritten; + strbuf_add(&data->len_buf, ptr, digits_remaining); + ptr += digits_remaining; + unwritten -= digits_remaining; + + if (data->len_buf.len == 4) { + data->remaining = packet_length(data->len_buf.buf); + if (data->remaining < 0) { + die(_("remote-curl: bad line length character: %.4s"), data->len_buf.buf); + } else if (data->remaining <= 1) { + data->remaining = 0; + } else if (data->remaining < 4) { + die(_("remote-curl: bad line length %d"), data->remaining); + } else { + data->remaining -= 4; + } + write_or_die(data->rpc->in, data->len_buf.buf, 4); + strbuf_reset(&data->len_buf); + } + } + + if (data->remaining) { + int remaining = data->remaining; + if (remaining > unwritten) + remaining = unwritten; + write_or_die(data->rpc->in, ptr, remaining); + ptr += remaining; + unwritten -= remaining; + data->remaining -= remaining; + } + } return size; } @@ -920,6 +958,8 @@ static int post_rpc(struct rpc_state *rpc, int flush_received) curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, rpc_in); rpc_in_data.rpc = rpc; rpc_in_data.slot = slot; + strbuf_init(&rpc_in_data.len_buf, 4); + rpc_in_data.remaining = 0; curl_easy_setopt(slot->curl, CURLOPT_FILE, &rpc_in_data); curl_easy_setopt(slot->curl, CURLOPT_FAILONERROR, 0); @@ -936,6 +976,9 @@ static int post_rpc(struct rpc_state *rpc, int flush_received) if (!rpc->any_written) err = -1; + if (rpc_in_data.remaining) + err = error(_("%d bytes are still expected"), rpc_in_data.remaining); + curl_slist_free_all(headers); free(gzip_body); return err; -- 2.26.2.706.g87896c9627