Skip to content

Commit 80d268f

Browse files
committed
Merge branch 'jk/protocol-cap-parse-fix'
The code to parse capability list for v0 on-wire protocol fell into an infinite loop when a capability appears multiple times, which has been corrected. * jk/protocol-cap-parse-fix: v0 protocol: use size_t for capability length/offset t5512: test "ls-remote --heads --symref" filtering with v0 and v2 t5512: allow any protocol version for filtered symref test t5512: add v2 support for "ls-remote --symref" test v0 protocol: fix sha1/sha256 confusion for capabilities^{} t5512: stop referring to "v1" protocol v0 protocol: fix infinite loop when parsing multi-valued capabilities
2 parents 0807e57 + 7ce4c8f commit 80d268f

File tree

8 files changed

+112
-90
lines changed

8 files changed

+112
-90
lines changed

builtin/receive-pack.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2096,7 +2096,7 @@ static struct command *read_head_info(struct packet_reader *reader,
20962096
const char *feature_list = reader->line + linelen + 1;
20972097
const char *hash = NULL;
20982098
const char *client_sid;
2099-
int len = 0;
2099+
size_t len = 0;
21002100
if (parse_feature_request(feature_list, "report-status"))
21012101
report_status = 1;
21022102
if (parse_feature_request(feature_list, "report-status-v2"))

connect.c

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222

2323
static char *server_capabilities_v1;
2424
static struct strvec server_capabilities_v2 = STRVEC_INIT;
25-
static const char *next_server_feature_value(const char *feature, int *len, int *offset);
25+
static const char *next_server_feature_value(const char *feature, size_t *len, size_t *offset);
2626

2727
static int check_ref(const char *name, unsigned int flags)
2828
{
@@ -205,10 +205,10 @@ static void parse_one_symref_info(struct string_list *symref, const char *val, i
205205
static void annotate_refs_with_symref_info(struct ref *ref)
206206
{
207207
struct string_list symref = STRING_LIST_INIT_DUP;
208-
int offset = 0;
208+
size_t offset = 0;
209209

210210
while (1) {
211-
int len;
211+
size_t len;
212212
const char *val;
213213

214214
val = next_server_feature_value("symref", &len, &offset);
@@ -231,7 +231,7 @@ static void annotate_refs_with_symref_info(struct ref *ref)
231231
static void process_capabilities(struct packet_reader *reader, int *linelen)
232232
{
233233
const char *feat_val;
234-
int feat_len;
234+
size_t feat_len;
235235
const char *line = reader->line;
236236
int nul_location = strlen(line);
237237
if (nul_location == *linelen)
@@ -263,7 +263,8 @@ static int process_dummy_ref(const struct packet_reader *reader)
263263
return 0;
264264
name++;
265265

266-
return oideq(null_oid(), &oid) && !strcmp(name, "capabilities^{}");
266+
return oideq(reader->hash_algo->null_oid, &oid) &&
267+
!strcmp(name, "capabilities^{}");
267268
}
268269

269270
static void check_no_capabilities(const char *line, int len)
@@ -595,9 +596,10 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,
595596
return list;
596597
}
597598

598-
const char *parse_feature_value(const char *feature_list, const char *feature, int *lenp, int *offset)
599+
const char *parse_feature_value(const char *feature_list, const char *feature, size_t *lenp, size_t *offset)
599600
{
600-
int len;
601+
const char *orig_start = feature_list;
602+
size_t len;
601603

602604
if (!feature_list)
603605
return NULL;
@@ -616,19 +618,19 @@ const char *parse_feature_value(const char *feature_list, const char *feature, i
616618
if (lenp)
617619
*lenp = 0;
618620
if (offset)
619-
*offset = found + len - feature_list;
621+
*offset = found + len - orig_start;
620622
return value;
621623
}
622624
/* feature with a value (e.g., "agent=git/1.2.3") */
623625
else if (*value == '=') {
624-
int end;
626+
size_t end;
625627

626628
value++;
627629
end = strcspn(value, " \t\n");
628630
if (lenp)
629631
*lenp = end;
630632
if (offset)
631-
*offset = value + end - feature_list;
633+
*offset = value + end - orig_start;
632634
return value;
633635
}
634636
/*
@@ -643,8 +645,8 @@ const char *parse_feature_value(const char *feature_list, const char *feature, i
643645

644646
int server_supports_hash(const char *desired, int *feature_supported)
645647
{
646-
int offset = 0;
647-
int len;
648+
size_t offset = 0;
649+
size_t len;
648650
const char *hash;
649651

650652
hash = next_server_feature_value("object-format", &len, &offset);
@@ -668,12 +670,12 @@ int parse_feature_request(const char *feature_list, const char *feature)
668670
return !!parse_feature_value(feature_list, feature, NULL, NULL);
669671
}
670672

671-
static const char *next_server_feature_value(const char *feature, int *len, int *offset)
673+
static const char *next_server_feature_value(const char *feature, size_t *len, size_t *offset)
672674
{
673675
return parse_feature_value(server_capabilities_v1, feature, len, offset);
674676
}
675677

676-
const char *server_feature_value(const char *feature, int *len)
678+
const char *server_feature_value(const char *feature, size_t *len)
677679
{
678680
return parse_feature_value(server_capabilities_v1, feature, len, NULL);
679681
}

connect.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@ int finish_connect(struct child_process *conn);
1212
int git_connection_is_socket(struct child_process *conn);
1313
int server_supports(const char *feature);
1414
int parse_feature_request(const char *features, const char *feature);
15-
const char *server_feature_value(const char *feature, int *len_ret);
15+
const char *server_feature_value(const char *feature, size_t *len_ret);
1616
int url_is_local_not_ssh(const char *url);
1717

1818
struct packet_reader;
1919
enum protocol_version discover_version(struct packet_reader *reader);
2020

2121
int server_supports_hash(const char *desired, int *feature_supported);
22-
const char *parse_feature_value(const char *feature_list, const char *feature, int *lenp, int *offset);
22+
const char *parse_feature_value(const char *feature_list, const char *feature, size_t *lenp, size_t *offset);
2323
int server_supports_v2(const char *c);
2424
void ensure_server_supports_v2(const char *c);
2525
int server_feature_v2(const char *c, const char **v);

fetch-pack.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1100,7 +1100,7 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args,
11001100
struct ref *ref = copy_ref_list(orig_ref);
11011101
struct object_id oid;
11021102
const char *agent_feature;
1103-
int agent_len;
1103+
size_t agent_len;
11041104
struct fetch_negotiator negotiator_alloc;
11051105
struct fetch_negotiator *negotiator;
11061106

@@ -1118,7 +1118,7 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args,
11181118
agent_supported = 1;
11191119
if (agent_len)
11201120
print_verbose(args, _("Server version is %.*s"),
1121-
agent_len, agent_feature);
1121+
(int)agent_len, agent_feature);
11221122
}
11231123

11241124
if (!server_supports("session-id"))

send-pack.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -539,7 +539,7 @@ int send_pack(struct send_pack_args *args,
539539
die(_("the receiving end does not support this repository's hash algorithm"));
540540

541541
if (args->push_cert != SEND_PACK_PUSH_CERT_NEVER) {
542-
int len;
542+
size_t len;
543543
push_cert_nonce = server_feature_value("push-cert", &len);
544544
if (push_cert_nonce) {
545545
reject_invalid_nonce(push_cert_nonce, len);

t/t5512-ls-remote.sh

Lines changed: 88 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,19 @@ generate_references () {
1515
done
1616
}
1717

18+
test_expect_success 'set up fake upload-pack' '
19+
# This can be used to simulate an upload-pack that just shows the
20+
# contents of the "input" file (prepared with the test-tool pkt-line
21+
# helper), and does not do any negotiation (since ls-remote does not
22+
# need it).
23+
write_script cat-input <<-\EOF
24+
# send our initial advertisement/response
25+
cat input
26+
# soak up the flush packet from the client
27+
cat
28+
EOF
29+
'
30+
1831
test_expect_success 'dies when no remote found' '
1932
test_must_fail git ls-remote
2033
'
@@ -231,22 +244,25 @@ test_expect_success 'protocol v2 supports hiderefs' '
231244

232245
test_expect_success 'ls-remote --symref' '
233246
git fetch origin &&
234-
echo "ref: refs/heads/main HEAD" >expect &&
247+
echo "ref: refs/heads/main HEAD" >expect.v2 &&
235248
generate_references \
236249
HEAD \
237-
refs/heads/main >>expect &&
250+
refs/heads/main >>expect.v2 &&
251+
echo "ref: refs/remotes/origin/main refs/remotes/origin/HEAD" >>expect.v2 &&
238252
oid=$(git rev-parse HEAD) &&
239-
echo "$oid refs/remotes/origin/HEAD" >>expect &&
253+
echo "$oid refs/remotes/origin/HEAD" >>expect.v2 &&
240254
generate_references \
241255
refs/remotes/origin/main \
242256
refs/tags/mark \
243257
refs/tags/mark1.1 \
244258
refs/tags/mark1.10 \
245-
refs/tags/mark1.2 >>expect &&
246-
# Protocol v2 supports sending symrefs for refs other than HEAD, so use
247-
# protocol v0 here.
248-
GIT_TEST_PROTOCOL_VERSION=0 git ls-remote --symref >actual &&
249-
test_cmp expect actual
259+
refs/tags/mark1.2 >>expect.v2 &&
260+
# v0 does not show non-HEAD symrefs
261+
grep -v "ref: refs/remotes" <expect.v2 >expect.v0 &&
262+
git -c protocol.version=0 ls-remote --symref >actual.v0 &&
263+
test_cmp expect.v0 actual.v0 &&
264+
git -c protocol.version=2 ls-remote --symref >actual.v2 &&
265+
test_cmp expect.v2 actual.v2
250266
'
251267

252268
test_expect_success 'ls-remote with filtered symref (refname)' '
@@ -255,76 +271,41 @@ test_expect_success 'ls-remote with filtered symref (refname)' '
255271
ref: refs/heads/main HEAD
256272
$rev HEAD
257273
EOF
258-
# Protocol v2 supports sending symrefs for refs other than HEAD, so use
259-
# protocol v0 here.
260-
GIT_TEST_PROTOCOL_VERSION=0 git ls-remote --symref . HEAD >actual &&
274+
git ls-remote --symref . HEAD >actual &&
261275
test_cmp expect actual
262276
'
263277

264-
test_expect_failure 'ls-remote with filtered symref (--heads)' '
278+
test_expect_success 'ls-remote with filtered symref (--heads)' '
265279
git symbolic-ref refs/heads/foo refs/tags/mark &&
266-
cat >expect <<-EOF &&
280+
cat >expect.v2 <<-EOF &&
267281
ref: refs/tags/mark refs/heads/foo
268282
$rev refs/heads/foo
269283
$rev refs/heads/main
270284
EOF
271-
# Protocol v2 supports sending symrefs for refs other than HEAD, so use
272-
# protocol v0 here.
273-
GIT_TEST_PROTOCOL_VERSION=0 git ls-remote --symref --heads . >actual &&
274-
test_cmp expect actual
285+
grep -v "^ref: refs/tags/" <expect.v2 >expect.v0 &&
286+
git -c protocol.version=0 ls-remote --symref --heads . >actual.v0 &&
287+
test_cmp expect.v0 actual.v0 &&
288+
git -c protocol.version=2 ls-remote --symref --heads . >actual.v2 &&
289+
test_cmp expect.v2 actual.v2
275290
'
276291

277-
test_expect_success 'ls-remote --symref omits filtered-out matches' '
278-
cat >expect <<-EOF &&
279-
$rev refs/heads/foo
280-
$rev refs/heads/main
292+
test_expect_success 'indicate no refs in v0 standards-compliant empty remote' '
293+
# Git does not produce an output like this, but it does match the
294+
# standard and is produced by other implementations like JGit. So
295+
# hard-code the case we care about.
296+
#
297+
# The actual capabilities do not matter; there are none that would
298+
# change how ls-remote behaves.
299+
oid=0000000000000000000000000000000000000000 &&
300+
test-tool pkt-line pack >input.q <<-EOF &&
301+
$oid capabilities^{}Qcaps-go-here
302+
0000
281303
EOF
282-
# Protocol v2 supports sending symrefs for refs other than HEAD, so use
283-
# protocol v0 here.
284-
GIT_TEST_PROTOCOL_VERSION=0 git ls-remote --symref --heads . >actual &&
285-
test_cmp expect actual &&
286-
GIT_TEST_PROTOCOL_VERSION=0 git ls-remote --symref . "refs/heads/*" >actual &&
287-
test_cmp expect actual
288-
'
289-
290-
test_lazy_prereq GIT_DAEMON '
291-
test_bool_env GIT_TEST_GIT_DAEMON true
292-
'
304+
q_to_nul <input.q >input &&
293305
294-
# This test spawns a daemon, so run it only if the user would be OK with
295-
# testing with git-daemon.
296-
test_expect_success PIPE,JGIT,GIT_DAEMON 'indicate no refs in standards-compliant empty remote' '
297-
test_set_port JGIT_DAEMON_PORT &&
298-
JGIT_DAEMON_PID= &&
299-
git init --bare empty.git &&
300-
>empty.git/git-daemon-export-ok &&
301-
mkfifo jgit_daemon_output &&
302-
{
303-
jgit daemon --port="$JGIT_DAEMON_PORT" . >jgit_daemon_output &
304-
JGIT_DAEMON_PID=$!
305-
} &&
306-
test_when_finished kill "$JGIT_DAEMON_PID" &&
307-
{
308-
read line &&
309-
case $line in
310-
Exporting*)
311-
;;
312-
*)
313-
echo "Expected: Exporting" &&
314-
false;;
315-
esac &&
316-
read line &&
317-
case $line in
318-
"Listening on"*)
319-
;;
320-
*)
321-
echo "Expected: Listening on" &&
322-
false;;
323-
esac
324-
} <jgit_daemon_output &&
325306
# --exit-code asks the command to exit with 2 when no
326307
# matching refs are found.
327-
test_expect_code 2 git ls-remote --exit-code git://localhost:$JGIT_DAEMON_PORT/empty.git
308+
test_expect_code 2 git ls-remote --exit-code --upload-pack=./cat-input .
328309
'
329310

330311
test_expect_success 'ls-remote works outside repository' '
@@ -345,19 +326,58 @@ test_expect_success 'ls-remote --sort fails gracefully outside repository' '
345326
test_expect_success 'ls-remote patterns work with all protocol versions' '
346327
git for-each-ref --format="%(objectname) %(refname)" \
347328
refs/heads/main refs/remotes/origin/main >expect &&
348-
git -c protocol.version=1 ls-remote . main >actual.v1 &&
349-
test_cmp expect actual.v1 &&
329+
git -c protocol.version=0 ls-remote . main >actual.v0 &&
330+
test_cmp expect actual.v0 &&
350331
git -c protocol.version=2 ls-remote . main >actual.v2 &&
351332
test_cmp expect actual.v2
352333
'
353334

354335
test_expect_success 'ls-remote prefixes work with all protocol versions' '
355336
git for-each-ref --format="%(objectname) %(refname)" \
356337
refs/heads/ refs/tags/ >expect &&
357-
git -c protocol.version=1 ls-remote --heads --tags . >actual.v1 &&
358-
test_cmp expect actual.v1 &&
338+
git -c protocol.version=0 ls-remote --heads --tags . >actual.v0 &&
339+
test_cmp expect actual.v0 &&
359340
git -c protocol.version=2 ls-remote --heads --tags . >actual.v2 &&
360341
test_cmp expect actual.v2
361342
'
362343

344+
test_expect_success 'v0 clients can handle multiple symrefs' '
345+
# Modern versions of Git will not return multiple symref capabilities
346+
# for v0, so we have to hard-code the response. Note that we will
347+
# always use both v0 and object-format=sha1 here, as the hard-coded
348+
# response reflects a server that only supports those.
349+
oid=1234567890123456789012345678901234567890 &&
350+
symrefs="symref=refs/remotes/origin/HEAD:refs/remotes/origin/main" &&
351+
symrefs="$symrefs symref=HEAD:refs/heads/main" &&
352+
353+
# Likewise we want to make sure our parser is not fooled by the string
354+
# "symref" appearing as part of an earlier cap. But there is no way to
355+
# do that via upload-pack, as arbitrary strings can appear only in a
356+
# "symref" value itself (where we skip past the values as a whole)
357+
# and "agent" (which always appears after "symref", so putting our
358+
# parser in a confused state is less interesting).
359+
caps="some other caps including a-fake-symref-cap" &&
360+
361+
test-tool pkt-line pack >input.q <<-EOF &&
362+
$oid HEADQ$caps $symrefs
363+
$oid refs/heads/main
364+
$oid refs/remotes/origin/HEAD
365+
$oid refs/remotes/origin/main
366+
0000
367+
EOF
368+
q_to_nul <input.q >input &&
369+
370+
cat >expect <<-EOF &&
371+
ref: refs/heads/main HEAD
372+
$oid HEAD
373+
$oid refs/heads/main
374+
ref: refs/remotes/origin/main refs/remotes/origin/HEAD
375+
$oid refs/remotes/origin/HEAD
376+
$oid refs/remotes/origin/main
377+
EOF
378+
379+
git ls-remote --symref --upload-pack=./cat-input . >actual &&
380+
test_cmp expect actual
381+
'
382+
363383
test_done

transport.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@ static struct ref *handshake(struct transport *transport, int for_push,
320320
struct git_transport_data *data = transport->data;
321321
struct ref *refs = NULL;
322322
struct packet_reader reader;
323-
int sid_len;
323+
size_t sid_len;
324324
const char *server_sid;
325325

326326
connect_setup(transport, for_push);

upload-pack.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1070,7 +1070,7 @@ static void receive_needs(struct upload_pack_data *data,
10701070
const char *features;
10711071
struct object_id oid_buf;
10721072
const char *arg;
1073-
int feature_len;
1073+
size_t feature_len;
10741074

10751075
reset_timeout(data->timeout);
10761076
if (packet_reader_read(reader) != PACKET_READ_NORMAL)

0 commit comments

Comments
 (0)