From 820d7650cc670d3e4195aad3a5343158c316e8fa Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 26 Jul 2017 10:24:20 -0700 Subject: connect: reject ssh hostname that begins with a dash When commands like "git fetch" talk with ssh://$rest_of_URL/, the code splits $rest_of_URL into components like host, port, etc., and then spawns the underlying "ssh" program by formulating argv[] array that has: - the path to ssh command taken from GIT_SSH_COMMAND, etc. - dashed options like '-batch' (for Tortoise), '-p ' as needed. - ssh_host, which is supposed to be the hostname parsed out of $rest_of_URL. - then the command to be run on the other side, e.g. git upload-pack. If the ssh_host ends up getting '-', the argv[] that is used to spawn the command becomes something like: { "ssh", "-p", "22", "-", "command", "to", "run", NULL } which obviously is bogus, but depending on the actual value of "", will make "ssh" parse and use it as an option. Prevent this by forbidding ssh_host that begins with a "-". Noticed-by: Joern Schneeweisz of Recurity Labs Reported-by: Brian at GitLab Signed-off-by: Junio C Hamano Reviewed-by: Jeff King Signed-off-by: Junio C Hamano --- connect.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'connect.c') diff --git a/connect.c b/connect.c index fd7ffe1840..0e8e05d83a 100644 --- a/connect.c +++ b/connect.c @@ -754,6 +754,9 @@ struct child_process *git_connect(int fd[2], const char *url, return NULL; } + if (ssh_host[0] == '-') + die("strange hostname '%s' blocked", ssh_host); + ssh = getenv("GIT_SSH_COMMAND"); if (!ssh) { const char *base; -- cgit 1.2.3-korg From 2491f77b90c2e5d47acbe7472c17e7de0af74f63 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Fri, 28 Jul 2017 15:25:45 -0400 Subject: connect: factor out "looks like command line option" check We reject hostnames that start with a dash because they may be confused for command-line options. Let's factor out that notion into a helper function, as we'll use it in more places. And while it's simple now, it's not clear if some systems might need more complex logic to handle all cases. Signed-off-by: Jeff King Reviewed-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- cache.h | 8 ++++++++ connect.c | 2 +- path.c | 5 +++++ 3 files changed, 14 insertions(+), 1 deletion(-) (limited to 'connect.c') diff --git a/cache.h b/cache.h index 1a2cec0b88..b9fc3a8e33 100644 --- a/cache.h +++ b/cache.h @@ -991,6 +991,14 @@ char *strip_path_suffix(const char *path, const char *suffix); int daemon_avoid_alias(const char *path); extern int is_ntfs_dotgit(const char *name); +/* + * Returns true iff "str" could be confused as a command-line option when + * passed to a sub-program like "ssh". Note that this has nothing to do with + * shell-quoting, which should be handled separately; we're assuming here that + * the string makes it verbatim to the sub-program. + */ +int looks_like_command_line_option(const char *str); + /** * Return a newly allocated string with the evaluation of * "$XDG_CONFIG_HOME/git/$filename" if $XDG_CONFIG_HOME is non-empty, otherwise diff --git a/connect.c b/connect.c index 0e8e05d83a..a0091acb1f 100644 --- a/connect.c +++ b/connect.c @@ -754,7 +754,7 @@ struct child_process *git_connect(int fd[2], const char *url, return NULL; } - if (ssh_host[0] == '-') + if (looks_like_command_line_option(ssh_host)) die("strange hostname '%s' blocked", ssh_host); ssh = getenv("GIT_SSH_COMMAND"); diff --git a/path.c b/path.c index 8b7e168129..b214ac3fe6 100644 --- a/path.c +++ b/path.c @@ -1178,6 +1178,11 @@ int is_ntfs_dotgit(const char *name) } } +int looks_like_command_line_option(const char *str) +{ + return str && str[0] == '-'; +} + char *xdg_config_home(const char *filename) { const char *home, *config_home; -- cgit 1.2.3-korg From 3be4cf09cd3d0747af3ecdb8dc3962a0969b731e Mon Sep 17 00:00:00 2001 From: Jeff King Date: Fri, 28 Jul 2017 15:26:50 -0400 Subject: connect: reject dashed arguments for proxy commands If you have a GIT_PROXY_COMMAND configured, we will run it with the host/port on the command-line. If a URL contains a mischievous host like "--foo", we don't know how the proxy command may handle it. It's likely to break, but it may also do something dangerous and unwanted (technically it could even do something useful, but that seems unlikely). We should err on the side of caution and reject this before we even run the command. The hostname check matches the one we do in a similar circumstance for ssh. The port check is not present for ssh, but there it's not necessary because the syntax is "-p ", and there's no ambiguity on the parsing side. It's not clear whether you can actually get a negative port to the proxy here or not. Doing: git fetch git://remote:-1234/repo.git keeps the "-1234" as part of the hostname, with the default port of 9418. But it's a good idea to keep this check close to the point of running the command to make it clear that there's no way to circumvent it (and at worst it serves as a belt-and-suspenders check). Signed-off-by: Jeff King Reviewed-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- connect.c | 5 +++++ t/t5532-fetch-proxy.sh | 5 +++++ 2 files changed, 10 insertions(+) (limited to 'connect.c') diff --git a/connect.c b/connect.c index a0091acb1f..bdf2ca08ac 100644 --- a/connect.c +++ b/connect.c @@ -553,6 +553,11 @@ static struct child_process *git_proxy_connect(int fd[2], char *host) get_host_and_port(&host, &port); + if (looks_like_command_line_option(host)) + die("strange hostname '%s' blocked", host); + if (looks_like_command_line_option(port)) + die("strange port '%s' blocked", port); + proxy = xmalloc(sizeof(*proxy)); child_process_init(proxy); argv_array_push(&proxy->args, git_proxy_command); diff --git a/t/t5532-fetch-proxy.sh b/t/t5532-fetch-proxy.sh index 5531bd1af4..d3b2651b61 100755 --- a/t/t5532-fetch-proxy.sh +++ b/t/t5532-fetch-proxy.sh @@ -40,4 +40,9 @@ test_expect_success 'fetch through proxy works' ' test_cmp expect actual ' +test_expect_success 'funny hostnames are rejected before running proxy' ' + test_must_fail git fetch git://-remote/repo.git 2>stderr && + ! grep "proxying for" stderr +' + test_done -- cgit 1.2.3-korg From aeeb2d496859419ac1ba1da1162d6f3610f7f1f3 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Fri, 28 Jul 2017 15:28:55 -0400 Subject: connect: reject paths that look like command line options If we get a repo path like "-repo.git", we may try to invoke "git-upload-pack -repo.git". This is going to fail, since upload-pack will interpret it as a set of bogus options. But let's reject this before we even run the sub-program, since we would not want to allow any mischief with repo names that actually are real command-line options. You can still ask for such a path via git-daemon, but there's no security problem there, because git-daemon enters the repo itself and then passes "." on the command line. Signed-off-by: Jeff King Reviewed-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- connect.c | 3 +++ t/t5810-proto-disable-local.sh | 23 +++++++++++++++++++++++ t/t5813-proto-disable-ssh.sh | 14 ++++++++++++++ 3 files changed, 40 insertions(+) (limited to 'connect.c') diff --git a/connect.c b/connect.c index bdf2ca08ac..d77d39771b 100644 --- a/connect.c +++ b/connect.c @@ -727,6 +727,9 @@ struct child_process *git_connect(int fd[2], const char *url, conn = xmalloc(sizeof(*conn)); child_process_init(conn); + if (looks_like_command_line_option(path)) + die("strange pathname '%s' blocked", path); + strbuf_addstr(&cmd, prog); strbuf_addch(&cmd, ' '); sq_quote_buf(&cmd, path); diff --git a/t/t5810-proto-disable-local.sh b/t/t5810-proto-disable-local.sh index 563592d8a8..c1ef99b85c 100755 --- a/t/t5810-proto-disable-local.sh +++ b/t/t5810-proto-disable-local.sh @@ -11,4 +11,27 @@ test_expect_success 'setup repository to clone' ' test_proto "file://" file "file://$PWD" test_proto "path" file . +test_expect_success 'setup repo with dash' ' + git init --bare repo.git && + git push repo.git HEAD && + mv repo.git "$PWD/-repo.git" +' + +# This will fail even without our rejection because upload-pack will +# complain about the bogus option. So let's make sure that GIT_TRACE +# doesn't show us even running upload-pack. +# +# We must also be sure to use "fetch" and not "clone" here, as the latter +# actually canonicalizes our input into an absolute path (which is fine +# to allow). +test_expect_success 'repo names starting with dash are rejected' ' + rm -f trace.out && + test_must_fail env GIT_TRACE="$PWD/trace.out" git fetch -- -repo.git && + ! grep upload-pack trace.out +' + +test_expect_success 'full paths still work' ' + git fetch "$PWD/-repo.git" +' + test_done diff --git a/t/t5813-proto-disable-ssh.sh b/t/t5813-proto-disable-ssh.sh index 0ecdceecd6..3f084ee306 100755 --- a/t/t5813-proto-disable-ssh.sh +++ b/t/t5813-proto-disable-ssh.sh @@ -26,4 +26,18 @@ test_expect_success 'hostnames starting with dash are rejected' ' ! grep ^ssh: stderr ' +test_expect_success 'setup repo with dash' ' + git init --bare remote/-repo.git && + git push remote/-repo.git HEAD +' + +test_expect_success 'repo names starting with dash are rejected' ' + test_must_fail git clone remote:-repo.git dash-path 2>stderr && + ! grep ^ssh: stderr +' + +test_expect_success 'full paths still work' ' + git clone "remote:$PWD/remote/-repo.git" dash-path +' + test_done -- cgit 1.2.3-korg