Skip to content

Commit b37fd14

Browse files
committed
Merge branch 'dl/remote-curl-deadlock-fix'
On-the-wire protocol v2 easily falls into a deadlock between the remote-curl helper and the fetch-pack process when the server side prematurely throws an error and disconnects. The communication has been updated to make it more robust. * dl/remote-curl-deadlock-fix: stateless-connect: send response end packet pkt-line: define PACKET_READ_RESPONSE_END remote-curl: error on incomplete packet pkt-line: extern packet_length() transport: extract common fetch_pack() call remote-curl: remove label indentation remote-curl: fix typo
2 parents ded44af + b0df0c1 commit b37fd14

18 files changed

+211
-30
lines changed

Documentation/gitremote-helpers.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -405,7 +405,9 @@ Supported if the helper has the "connect" capability.
405405
trying to fall back). After line feed terminating the positive
406406
(empty) response, the output of the service starts. Messages
407407
(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
409411
expect the server to store any state in between request-response
410412
pairs. After the connection ends, the remote helper exits.
411413
+

Documentation/technical/protocol-v2.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ In protocol v2 these special packets will have the following semantics:
3333

3434
* '0000' Flush Packet (flush-pkt) - indicates the end of a message
3535
* '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
3638

3739
Initial Client Request
3840
----------------------

builtin/fetch-pack.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
224224
version = discover_version(&reader);
225225
switch (version) {
226226
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);
228228
break;
229229
case protocol_v1:
230230
case protocol_v0:

connect.c

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ enum protocol_version discover_version(struct packet_reader *reader)
127127
die_initial_contact(0);
128128
case PACKET_READ_FLUSH:
129129
case PACKET_READ_DELIM:
130+
case PACKET_READ_RESPONSE_END:
130131
version = protocol_v0;
131132
break;
132133
case PACKET_READ_NORMAL:
@@ -310,6 +311,7 @@ struct ref **get_remote_heads(struct packet_reader *reader,
310311
state = EXPECTING_DONE;
311312
break;
312313
case PACKET_READ_DELIM:
314+
case PACKET_READ_RESPONSE_END:
313315
die(_("invalid packet"));
314316
}
315317

@@ -404,10 +406,21 @@ static int process_ref_v2(const char *line, struct ref ***list)
404406
return ret;
405407
}
406408

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+
407419
struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,
408420
struct ref **list, int for_push,
409421
const struct argv_array *ref_prefixes,
410-
const struct string_list *server_options)
422+
const struct string_list *server_options,
423+
int stateless_rpc)
411424
{
412425
int i;
413426
*list = NULL;
@@ -444,6 +457,9 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,
444457
if (reader->status != PACKET_READ_FLUSH)
445458
die(_("expected flush after ref listing"));
446459

460+
check_stateless_delimiter(stateless_rpc, reader,
461+
_("expected response end packet after ref listing"));
462+
447463
return list;
448464
}
449465

connect.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,8 @@ int server_supports_v2(const char *c, int die_on_error);
2222
int server_supports_feature(const char *c, const char *feature,
2323
int die_on_error);
2424

25+
void check_stateless_delimiter(int stateless_rpc,
26+
struct packet_reader *reader,
27+
const char *error);
28+
2529
#endif

fetch-pack.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1451,6 +1451,13 @@ enum fetch_state {
14511451
FETCH_DONE,
14521452
};
14531453

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+
14541461
static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
14551462
int fd[2],
14561463
const struct ref *orig_ref,
@@ -1535,13 +1542,18 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
15351542
/* Process ACKs/NAKs */
15361543
switch (process_acks(negotiator, &reader, &common)) {
15371544
case READY:
1545+
/*
1546+
* Don't check for response delimiter; get_pack() will
1547+
* read the rest of this response.
1548+
*/
15381549
state = FETCH_GET_PACK;
15391550
break;
15401551
case COMMON_FOUND:
15411552
in_vain = 0;
15421553
seen_ack = 1;
15431554
/* fallthrough */
15441555
case NO_COMMON_FOUND:
1556+
do_check_stateless_delimiter(args, &reader);
15451557
state = FETCH_SEND_REQUEST;
15461558
break;
15471559
}
@@ -1561,6 +1573,7 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
15611573
process_section_header(&reader, "packfile", 0);
15621574
if (get_pack(args, fd, pack_lockfile, sought, nr_sought))
15631575
die(_("git fetch-pack: fetch failed."));
1576+
do_check_stateless_delimiter(args, &reader);
15641577

15651578
state = FETCH_DONE;
15661579
break;

pkt-line.c

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,13 @@ void packet_delim(int fd)
9999
die_errno(_("unable to write delim packet"));
100100
}
101101

102+
void packet_response_end(int fd)
103+
{
104+
packet_trace("0002", 4, 1);
105+
if (write_in_full(fd, "0002", 4) < 0)
106+
die_errno(_("unable to write stateless separator packet"));
107+
}
108+
102109
int packet_flush_gently(int fd)
103110
{
104111
packet_trace("0000", 4, 1);
@@ -306,10 +313,10 @@ static int get_packet_data(int fd, char **src_buf, size_t *src_size,
306313
return ret;
307314
}
308315

309-
static int packet_length(const char *linelen)
316+
int packet_length(const char lenbuf_hex[4])
310317
{
311-
int val = hex2chr(linelen);
312-
return (val < 0) ? val : (val << 8) | hex2chr(linelen + 2);
318+
int val = hex2chr(lenbuf_hex);
319+
return (val < 0) ? val : (val << 8) | hex2chr(lenbuf_hex + 2);
313320
}
314321

315322
enum packet_read_status packet_read_with_status(int fd, char **src_buffer,
@@ -337,6 +344,10 @@ enum packet_read_status packet_read_with_status(int fd, char **src_buffer,
337344
packet_trace("0001", 4, 0);
338345
*pktlen = 0;
339346
return PACKET_READ_DELIM;
347+
} else if (len == 2) {
348+
packet_trace("0002", 4, 0);
349+
*pktlen = 0;
350+
return PACKET_READ_RESPONSE_END;
340351
} else if (len < 4) {
341352
die(_("protocol error: bad line length %d"), len);
342353
}

pkt-line.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
*/
2323
void packet_flush(int fd);
2424
void packet_delim(int fd);
25+
void packet_response_end(int fd);
2526
void packet_write_fmt(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3)));
2627
void packet_buf_flush(struct strbuf *buf);
2728
void packet_buf_delim(struct strbuf *buf);
@@ -74,6 +75,15 @@ int write_packetized_from_buf(const char *src_in, size_t len, int fd_out);
7475
int packet_read(int fd, char **src_buffer, size_t *src_len, char
7576
*buffer, unsigned size, int options);
7677

78+
/*
79+
* Convert a four hex digit packet line length header into its numeric
80+
* representation.
81+
*
82+
* If lenbuf_hex contains non-hex characters, return -1. Otherwise, return the
83+
* numeric value of the length header.
84+
*/
85+
int packet_length(const char lenbuf_hex[4]);
86+
7787
/*
7888
* Read a packetized line into a buffer like the 'packet_read()' function but
7989
* returns an 'enum packet_read_status' which indicates the status of the read.
@@ -85,6 +95,7 @@ enum packet_read_status {
8595
PACKET_READ_NORMAL,
8696
PACKET_READ_FLUSH,
8797
PACKET_READ_DELIM,
98+
PACKET_READ_RESPONSE_END,
8899
};
89100
enum packet_read_status packet_read_with_status(int fd, char **src_buffer,
90101
size_t *src_len, char *buffer,

remote-curl.c

Lines changed: 65 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,8 @@ static int rpc_read_from_out(struct rpc_state *rpc, int options,
601601
case PACKET_READ_FLUSH:
602602
memcpy(buf - 4, "0000", 4);
603603
break;
604+
case PACKET_READ_RESPONSE_END:
605+
die(_("remote server sent stateless separator"));
604606
}
605607
}
606608

@@ -643,7 +645,7 @@ static size_t rpc_out(void *ptr, size_t eltsize,
643645
return 0;
644646
}
645647
/*
646-
* If avail is non-zerp, the line length for the flush still
648+
* If avail is non-zero, the line length for the flush still
647649
* hasn't been fully sent. Proceed with sending the line
648650
* length.
649651
*/
@@ -679,9 +681,55 @@ static curlioerr rpc_ioctl(CURL *handle, int cmd, void *clientp)
679681
}
680682
#endif
681683

684+
struct check_pktline_state {
685+
char len_buf[4];
686+
int len_filled;
687+
int remaining;
688+
};
689+
690+
static void check_pktline(struct check_pktline_state *state, const char *ptr, size_t size)
691+
{
692+
while (size) {
693+
if (!state->remaining) {
694+
int digits_remaining = 4 - state->len_filled;
695+
if (digits_remaining > size)
696+
digits_remaining = size;
697+
memcpy(&state->len_buf[state->len_filled], ptr, digits_remaining);
698+
state->len_filled += digits_remaining;
699+
ptr += digits_remaining;
700+
size -= digits_remaining;
701+
702+
if (state->len_filled == 4) {
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 {
711+
state->remaining -= 4;
712+
}
713+
state->len_filled = 0;
714+
}
715+
}
716+
717+
if (state->remaining) {
718+
int remaining = state->remaining;
719+
if (remaining > size)
720+
remaining = size;
721+
ptr += remaining;
722+
size -= remaining;
723+
state->remaining -= remaining;
724+
}
725+
}
726+
}
727+
682728
struct rpc_in_data {
683729
struct rpc_state *rpc;
684730
struct active_request_slot *slot;
731+
int check_pktline;
732+
struct check_pktline_state pktline_state;
685733
};
686734

687735
/*
@@ -702,6 +750,8 @@ static size_t rpc_in(char *ptr, size_t eltsize,
702750
return size;
703751
if (size)
704752
data->rpc->any_written = 1;
753+
if (data->check_pktline)
754+
check_pktline(&data->pktline_state, ptr, size);
705755
write_or_die(data->rpc->in, ptr, size);
706756
return size;
707757
}
@@ -778,7 +828,7 @@ static curl_off_t xcurl_off_t(size_t len)
778828
* If flush_received is true, do not attempt to read any more; just use what's
779829
* in rpc->buf.
780830
*/
781-
static int post_rpc(struct rpc_state *rpc, int flush_received)
831+
static int post_rpc(struct rpc_state *rpc, int stateless_connect, int flush_received)
782832
{
783833
struct active_request_slot *slot;
784834
struct curl_slist *headers = http_copy_default_headers();
@@ -920,6 +970,8 @@ static int post_rpc(struct rpc_state *rpc, int flush_received)
920970
curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, rpc_in);
921971
rpc_in_data.rpc = rpc;
922972
rpc_in_data.slot = slot;
973+
rpc_in_data.check_pktline = stateless_connect;
974+
memset(&rpc_in_data.pktline_state, 0, sizeof(rpc_in_data.pktline_state));
923975
curl_easy_setopt(slot->curl, CURLOPT_FILE, &rpc_in_data);
924976
curl_easy_setopt(slot->curl, CURLOPT_FAILONERROR, 0);
925977

@@ -936,6 +988,14 @@ static int post_rpc(struct rpc_state *rpc, int flush_received)
936988
if (!rpc->any_written)
937989
err = -1;
938990

991+
if (rpc_in_data.pktline_state.len_filled)
992+
err = error(_("%d bytes of length header were received"), rpc_in_data.pktline_state.len_filled);
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+
939999
curl_slist_free_all(headers);
9401000
free(gzip_body);
9411001
return err;
@@ -985,7 +1045,7 @@ static int rpc_service(struct rpc_state *rpc, struct discovery *heads,
9851045
break;
9861046
rpc->pos = 0;
9871047
rpc->len = n;
988-
err |= post_rpc(rpc, 0);
1048+
err |= post_rpc(rpc, 0, 0);
9891049
}
9901050

9911051
close(client.in);
@@ -1276,7 +1336,7 @@ static void parse_push(struct strbuf *buf)
12761336
if (ret)
12771337
exit(128); /* error already reported */
12781338

1279-
free_specs:
1339+
free_specs:
12801340
argv_array_clear(&specs);
12811341
}
12821342

@@ -1342,7 +1402,7 @@ static int stateless_connect(const char *service_name)
13421402
BUG("The entire rpc->buf should be larger than LARGE_PACKET_MAX");
13431403
if (status == PACKET_READ_EOF)
13441404
break;
1345-
if (post_rpc(&rpc, status == PACKET_READ_FLUSH))
1405+
if (post_rpc(&rpc, 1, status == PACKET_READ_FLUSH))
13461406
/* We would have an err here */
13471407
break;
13481408
/* Reset the buffer for next request */

remote.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,8 @@ struct ref **get_remote_heads(struct packet_reader *reader,
179179
struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,
180180
struct ref **list, int for_push,
181181
const struct argv_array *ref_prefixes,
182-
const struct string_list *server_options);
182+
const struct string_list *server_options,
183+
int stateless_rpc);
183184

184185
int resolve_remote_symref(struct ref *ref, struct ref *list);
185186

0 commit comments

Comments
 (0)