diff --git a/connect.c b/connect.c index 062e133aa..372ac5ad9 100644 --- a/connect.c +++ b/connect.c @@ -670,10 +670,20 @@ struct child_process *git_connect(int fd[2], const char *url, printf("Diag: path=%s\n", path ? path : "NULL"); conn = NULL; } else if (protocol == PROTO_GIT) { + /* + * Set up virtual host information based on where we will + * connect, unless the user has overridden us in + * the environment. + */ + char *target_host = getenv("GIT_OVERRIDE_VIRTUAL_HOST"); + if (target_host) + target_host = xstrdup(target_host); + else + target_host = xstrdup(hostandport); + /* These underlying connection commands die() if they * cannot connect. */ - char *target_host = xstrdup(hostandport); if (git_use_proxy(hostandport)) conn = git_proxy_connect(fd, hostandport); else diff --git a/daemon.c b/daemon.c index 09fa652fd..c3edd960e 100644 --- a/daemon.c +++ b/daemon.c @@ -536,6 +536,45 @@ static void parse_host_and_port(char *hostport, char **host, } } +/* + * Sanitize a string from the client so that it's OK to be inserted into a + * filesystem path. Specifically, we disallow slashes, runs of "..", and + * trailing and leading dots, which means that the client cannot escape + * our base path via ".." traversal. + */ +static void sanitize_client_strbuf(struct strbuf *out, const char *in) +{ + for (; *in; in++) { + if (*in == '/') + continue; + if (*in == '.' && (!out->len || out->buf[out->len - 1] == '.')) + continue; + strbuf_addch(out, *in); + } + + while (out->len && out->buf[out->len - 1] == '.') + strbuf_setlen(out, out->len - 1); +} + +static char *sanitize_client(const char *in) +{ + struct strbuf out = STRBUF_INIT; + sanitize_client_strbuf(&out, in); + return strbuf_detach(&out, NULL); +} + +/* + * Like sanitize_client, but we also perform any canonicalization + * to make life easier on the admin. + */ +static char *canonicalize_client(const char *in) +{ + struct strbuf out = STRBUF_INIT; + sanitize_client_strbuf(&out, in); + strbuf_tolower(&out); + return strbuf_detach(&out, NULL); +} + /* * Read the host as supplied by the client connection. */ @@ -557,10 +596,10 @@ static void parse_host_arg(char *extra_args, int buflen) parse_host_and_port(val, &host, &port); if (port) { free(tcp_port); - tcp_port = xstrdup(port); + tcp_port = sanitize_client(port); } free(hostname); - hostname = xstrdup_tolower(host); + hostname = canonicalize_client(host); hostname_lookup_done = 0; } @@ -597,8 +636,9 @@ static void lookup_hostname(void) ip_address = xstrdup(addrbuf); free(canon_hostname); - canon_hostname = xstrdup(ai->ai_canonname ? - ai->ai_canonname : ip_address); + canon_hostname = ai->ai_canonname ? + sanitize_client(ai->ai_canonname) : + xstrdup(ip_address); freeaddrinfo(ai); } @@ -620,7 +660,7 @@ static void lookup_hostname(void) addrbuf, sizeof(addrbuf)); free(canon_hostname); - canon_hostname = xstrdup(hent->h_name); + canon_hostname = sanitize_client(hent->h_name); free(ip_address); ip_address = xstrdup(addrbuf); } diff --git a/t/t5570-git-daemon.sh b/t/t5570-git-daemon.sh index 6b1637995..b7e283252 100755 --- a/t/t5570-git-daemon.sh +++ b/t/t5570-git-daemon.sh @@ -141,5 +141,32 @@ test_expect_success 'push disabled' "test_remote_error 'service not enab test_expect_success 'read access denied' "test_remote_error -x 'no such repository' fetch repo.git " test_expect_success 'not exported' "test_remote_error -n 'repository not exported' fetch repo.git " +stop_git_daemon +start_git_daemon --interpolated-path="$GIT_DAEMON_DOCUMENT_ROOT_PATH/%H%D" + +test_expect_success 'access repo via interpolated hostname' ' + repo="$GIT_DAEMON_DOCUMENT_ROOT_PATH/localhost/interp.git" && + git init --bare "$repo" && + git push "$repo" HEAD && + >"$repo"/git-daemon-export-ok && + rm -rf tmp.git && + GIT_OVERRIDE_VIRTUAL_HOST=localhost \ + git clone --bare "$GIT_DAEMON_URL/interp.git" tmp.git && + rm -rf tmp.git && + GIT_OVERRIDE_VIRTUAL_HOST=LOCALHOST \ + git clone --bare "$GIT_DAEMON_URL/interp.git" tmp.git +' + +test_expect_success 'hostname cannot break out of directory' ' + rm -rf tmp.git && + repo="$GIT_DAEMON_DOCUMENT_ROOT_PATH/../escape.git" && + git init --bare "$repo" && + git push "$repo" HEAD && + >"$repo"/git-daemon-export-ok && + test_must_fail \ + env GIT_OVERRIDE_VIRTUAL_HOST=.. \ + git clone --bare "$GIT_DAEMON_URL/escape.git" tmp.git +' + stop_git_daemon test_done