Skip to content

Commit eaa0fd6

Browse files
peffgitster
authored andcommitted
git_connect(): fix corner cases in downgrading v2 to v0
There's code in git_connect() that checks whether we are doing a push with protocol_v2, and if so, drops us to protocol_v0 (since we know how to do v2 only for fetches). But it misses some corner cases: 1. it checks the "prog" variable, which is actually the path to receive-pack on the remote side. By default this is just "git-receive-pack", but it could be an arbitrary string (like "/path/to/git receive-pack", etc). We'd accidentally stay in v2 mode in this case. 2. besides "receive-pack" and "upload-pack", there's one other value we'd expect: "upload-archive" for handling "git archive --remote". Like receive-pack, this doesn't understand v2, and should use the v0 protocol. In practice, neither of these causes bugs in the real world so far. We do send a "we understand v2" probe to the server, but since no server implements v2 for anything but upload-pack, it's simply ignored. But this would eventually become a problem if we do implement v2 for those endpoints, as older clients would falsely claim to understand it, leading to a server response they can't parse. We can fix (1) by passing in both the program path and the "name" of the operation. I treat the name as a string here, because that's the pattern set in transport_connect(), which is one of our callers (we were simply throwing away the "name" value there before). We can fix (2) by allowing only known-v2 protocols ("upload-pack"), rather than blocking unknown ones ("receive-pack" and "upload-archive"). That will mean whoever eventually implements v2 push will have to adjust this list, but that's reasonable. We'll do the safe, conservative thing (sticking to v0) by default, and anybody working on v2 will quickly realize this spot needs to be updated. The new tests cover the receive-pack and upload-archive cases above, and re-confirm that we allow v2 with an arbitrary "--upload-pack" path (that already worked before this patch, of course, but it would be an easy thing to break if we flipped the allow/block logic without also handling "name" separately). Here are a few miscellaneous implementation notes, since I had to do a little head-scratching to understand who calls what: - transport_connect() is called only for git-upload-archive. For non-http git remotes, that resolves to the virtual connect_git() function (which then calls git_connect(); confused yet?). So plumbing through "name" in connect_git() covers that. - for regular fetches and pushes, callers use higher-level functions like transport_fetch_refs(). For non-http git remotes, that means calling git_connect() under the hood via connect_setup(). And that uses the "for_push" flag to decide which name to use. - likewise, plumbing like fetch-pack and send-pack may call git_connect() directly; they each know which name to use. - for remote helpers (including http), we already have separate parameters for "name" and "exec" (another name for "prog"). In process_connect_service(), we feed the "name" to the helper via "connect" or "stateless-connect" directives. There's also a "servpath" option, which can be used to tell the helper about the "exec" path. But no helpers we implement support it! For http it would be useless anyway (no reasonable server implementation will allow you to send a shell command to run the server). In theory it would be useful for more obscure helpers like remote-ext, but even there it is not implemented. It's tempting to get rid of it simply to reduce confusion, but we have publicly documented it since it was added in fa8c097 (Support remote helpers implementing smart transports, 2009-12-09), so it's possible some helper in the wild is using it. - So for v2, helpers (again, including http) are mainly used via stateless-connect, driven by the main program. But they do still need to decide whether to do a v2 probe. And so there's similar logic in remote-curl.c's discover_refs() that looks for "git-receive-pack". But it's not buggy in the same way. Since it doesn't support servpath, it is always dealing with a "service" string like "git-receive-pack". And since it doesn't support straight "connect", it can't be used for "upload-archive". So we could leave that spot alone. But I've updated it here to match the logic we're changing in connect_git(). That seems like the least confusing thing for somebody who has to touch both of these spots later (say, to add v2 push support). I didn't add a new test to make sure this doesn't break anything; we already have several tests (in t5551 and elsewhere) that make sure we are using v2 over http. Signed-off-by: Jeff King <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 7556e5d commit eaa0fd6

File tree

7 files changed

+47
-13
lines changed

7 files changed

+47
-13
lines changed

builtin/fetch-pack.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -211,8 +211,8 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
211211
int flags = args.verbose ? CONNECT_VERBOSE : 0;
212212
if (args.diag_url)
213213
flags |= CONNECT_DIAG_URL;
214-
conn = git_connect(fd, dest, args.uploadpack,
215-
flags);
214+
conn = git_connect(fd, dest, "git-upload-pack",
215+
args.uploadpack, flags);
216216
if (!conn)
217217
return args.diag_url ? 0 : 1;
218218
}

builtin/send-pack.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
274274
fd[0] = 0;
275275
fd[1] = 1;
276276
} else {
277-
conn = git_connect(fd, dest, receivepack,
277+
conn = git_connect(fd, dest, "git-receive-pack", receivepack,
278278
args.verbose ? CONNECT_VERBOSE : 0);
279279
}
280280

connect.c

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1359,6 +1359,7 @@ static void fill_ssh_args(struct child_process *conn, const char *ssh_host,
13591359
* the connection failed).
13601360
*/
13611361
struct child_process *git_connect(int fd[2], const char *url,
1362+
const char *name,
13621363
const char *prog, int flags)
13631364
{
13641365
char *hostandport, *path;
@@ -1368,10 +1369,11 @@ struct child_process *git_connect(int fd[2], const char *url,
13681369

13691370
/*
13701371
* NEEDSWORK: If we are trying to use protocol v2 and we are planning
1371-
* to perform a push, then fallback to v0 since the client doesn't know
1372-
* how to push yet using v2.
1372+
* to perform any operation that doesn't involve upload-pack (i.e., a
1373+
* fetch, ls-remote, etc), then fallback to v0 since we don't know how
1374+
* to do anything else (like push or remote archive) via v2.
13731375
*/
1374-
if (version == protocol_v2 && !strcmp("git-receive-pack", prog))
1376+
if (version == protocol_v2 && strcmp("git-upload-pack", name))
13751377
version = protocol_v0;
13761378

13771379
/* Without this we cannot rely on waitpid() to tell

connect.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
#define CONNECT_DIAG_URL (1u << 1)
88
#define CONNECT_IPV4 (1u << 2)
99
#define CONNECT_IPV6 (1u << 3)
10-
struct child_process *git_connect(int fd[2], const char *url, const char *prog, int flags);
10+
struct child_process *git_connect(int fd[2], const char *url, const char *name, const char *prog, int flags);
1111
int finish_connect(struct child_process *conn);
1212
int git_connection_is_socket(struct child_process *conn);
1313
int server_supports(const char *feature);

remote-curl.c

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -472,10 +472,11 @@ static struct discovery *discover_refs(const char *service, int for_push)
472472

473473
/*
474474
* NEEDSWORK: If we are trying to use protocol v2 and we are planning
475-
* to perform a push, then fallback to v0 since the client doesn't know
476-
* how to push yet using v2.
475+
* to perform any operation that doesn't involve upload-pack (i.e., a
476+
* fetch, ls-remote, etc), then fallback to v0 since we don't know how
477+
* to do anything else (like push or remote archive) via v2.
477478
*/
478-
if (version == protocol_v2 && !strcmp("git-receive-pack", service))
479+
if (version == protocol_v2 && strcmp("git-upload-pack", service))
479480
version = protocol_v0;
480481

481482
/* Add the extra Git-Protocol header */

t/t5702-protocol-v2.sh

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -728,6 +728,33 @@ test_expect_success 'file:// --negotiate-only with protocol v0' '
728728
test_i18ngrep "negotiate-only requires protocol v2" err
729729
'
730730

731+
test_expect_success 'push with custom path does not request v2' '
732+
rm -f env.trace &&
733+
git -C client push \
734+
--receive-pack="env >../env.trace; git-receive-pack" \
735+
origin HEAD:refs/heads/custom-push-test &&
736+
test_path_is_file env.trace &&
737+
! grep ^GIT_PROTOCOL env.trace
738+
'
739+
740+
test_expect_success 'fetch with custom path does request v2' '
741+
rm -f env.trace &&
742+
git -C client fetch \
743+
--upload-pack="env >../env.trace; git-upload-pack" \
744+
origin HEAD &&
745+
grep ^GIT_PROTOCOL=version=2 env.trace
746+
'
747+
748+
test_expect_success 'archive with custom path does not request v2' '
749+
rm -f env.trace &&
750+
git -C client archive \
751+
--exec="env >../env.trace; git-upload-archive" \
752+
--remote=origin \
753+
HEAD >/dev/null &&
754+
test_path_is_file env.trace &&
755+
! grep ^GIT_PROTOCOL env.trace
756+
'
757+
731758
# Test protocol v2 with 'http://' transport
732759
#
733760
. "$TEST_DIRECTORY"/lib-httpd.sh

transport.c

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -275,8 +275,12 @@ static int connect_setup(struct transport *transport, int for_push)
275275
}
276276

277277
data->conn = git_connect(data->fd, transport->url,
278-
for_push ? data->options.receivepack :
279-
data->options.uploadpack,
278+
for_push ?
279+
"git-receive-pack" :
280+
"git-upload-pack",
281+
for_push ?
282+
data->options.receivepack :
283+
data->options.uploadpack,
280284
flags);
281285

282286
return 0;
@@ -877,7 +881,7 @@ static int connect_git(struct transport *transport, const char *name,
877881
{
878882
struct git_transport_data *data = transport->data;
879883
data->conn = git_connect(data->fd, transport->url,
880-
executable, 0);
884+
name, executable, 0);
881885
fd[0] = data->fd[0];
882886
fd[1] = data->fd[1];
883887
return 0;

0 commit comments

Comments
 (0)