diff options
-rw-r--r-- | connect.c | 12 | ||||
-rw-r--r-- | daemon.c | 50 | ||||
-rwxr-xr-x | t/t5570-git-daemon.sh | 27 |
3 files changed, 83 insertions, 6 deletions
@@ -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 @@ -537,6 +537,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. */ static void parse_host_arg(char *extra_args, int buflen) @@ -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 6b16379951..b7e283252d 100755 --- a/t/t5570-git-daemon.sh +++ b/t/t5570-git-daemon.sh @@ -142,4 +142,31 @@ test_expect_success 'read access denied' "test_remote_error -x 'no such reposito 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 |