Skip to content

Commit 88a2dc6

Browse files
pks-tgitster
authored andcommitted
upload-pack: don't ACK non-commits repeatedly in protocol v2
When a client performs a fetch or clone they can optionally send "have" lines to tell the server which objects they already have available locally. These object IDs are stored by the server in an object array so that it can remember any objects it doesn't have to include in the pack sent to the client. While there isn't any reason to do so, clients are free to send the same "have" line repeatedly. git-upload-pack(1) already knows to handle this well: every commit it has seen via a "have" line gets marked with the `THEY_HAVE` flag, and if such a commit is seen repeatedly we know to not process it another time. This also has the effect that we only store the object ID once, only, in the `have_obj` array. There is an edge case though: if the client sends an object ID that does not refer to a commit we neither store nor check the `THEY_HAVE` flag. This means that we repeatedly store the same object ID in our `have_obj` array, with two consequences: - In protocol v2 we deduplicate ACKs for commits, but not for any other objects as we send ACKs for every object ID in the `have_obj` array. - The `have_obj` array can grow in size indefinitely with both protocols. The potentially-more-serious issue is the second one, as we basically have a way for an adversary to allocate arbitrarily large buffers now. Ultimately, this doesn't seem to be all that serious though: on my machine, the growth of that array is at around 4MB/s, and after roughly five minutes I was only at 1GB RSS. So this is concerning, but only mildly so. Fix this bug by storing the `THEY_HAVE` flag independent of the object type so that we don't store duplicate object IDs in `have_obj` anymore. Signed-off-by: Patrick Steinhardt <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 7a57fb1 commit 88a2dc6

File tree

2 files changed

+48
-10
lines changed

2 files changed

+48
-10
lines changed

t/t5530-upload-pack-error.sh

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,4 +96,43 @@ test_expect_success 'fetch fails' '
9696
test_must_fail git -C foo fetch .. main
9797
'
9898

99+
test_expect_success 'upload-pack ACKs repeated non-commit objects repeatedly (protocol v0)' '
100+
commit_id=$(git rev-parse HEAD) &&
101+
tree_id=$(git rev-parse HEAD^{tree}) &&
102+
test-tool pkt-line pack >request <<-EOF &&
103+
want $commit_id
104+
0000
105+
have $tree_id
106+
have $tree_id
107+
0000
108+
EOF
109+
git upload-pack --stateless-rpc . <request >actual &&
110+
depacketize <actual >actual.raw &&
111+
grep ^ACK actual.raw >actual.acks &&
112+
cat >expect <<-EOF &&
113+
ACK $tree_id
114+
ACK $tree_id
115+
EOF
116+
test_cmp expect actual.acks
117+
'
118+
119+
test_expect_success 'upload-pack ACKs repeated non-commit objects once only (protocol v2)' '
120+
commit_id=$(git rev-parse HEAD) &&
121+
tree_id=$(git rev-parse HEAD^{tree}) &&
122+
test-tool pkt-line pack >request <<-EOF &&
123+
command=fetch
124+
object-format=$(test_oid algo)
125+
0001
126+
want $commit_id
127+
have $tree_id
128+
have $tree_id
129+
0000
130+
EOF
131+
GIT_PROTOCOL=version=2 git upload-pack . <request >actual &&
132+
depacketize <actual >actual.raw &&
133+
grep ^ACK actual.raw >actual.acks &&
134+
echo "ACK $tree_id" >expect &&
135+
test_cmp expect actual.acks
136+
'
137+
99138
test_done

upload-pack.c

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -476,32 +476,31 @@ static void create_pack_file(struct upload_pack_data *pack_data,
476476

477477
static int do_got_oid(struct upload_pack_data *data, const struct object_id *oid)
478478
{
479-
int we_knew_they_have = 0;
480479
struct object *o = parse_object_with_flags(the_repository, oid,
481480
PARSE_OBJECT_SKIP_HASH_CHECK |
482481
PARSE_OBJECT_DISCARD_TREE);
483482

484483
if (!o)
485484
die("oops (%s)", oid_to_hex(oid));
485+
486486
if (o->type == OBJ_COMMIT) {
487487
struct commit_list *parents;
488488
struct commit *commit = (struct commit *)o;
489-
if (o->flags & THEY_HAVE)
490-
we_knew_they_have = 1;
491-
else
492-
o->flags |= THEY_HAVE;
489+
493490
if (!data->oldest_have || (commit->date < data->oldest_have))
494491
data->oldest_have = commit->date;
495492
for (parents = commit->parents;
496493
parents;
497494
parents = parents->next)
498495
parents->item->object.flags |= THEY_HAVE;
499496
}
500-
if (!we_knew_they_have) {
501-
add_object_array(o, NULL, &data->have_obj);
502-
return 1;
503-
}
504-
return 0;
497+
498+
if (o->flags & THEY_HAVE)
499+
return 0;
500+
o->flags |= THEY_HAVE;
501+
502+
add_object_array(o, NULL, &data->have_obj);
503+
return 1;
505504
}
506505

507506
static int got_oid(struct upload_pack_data *data,

0 commit comments

Comments
 (0)