Merge branch 'jk/daemon-interpolate'
authorJunio C Hamano <gitster@pobox.com>
Tue, 3 Mar 2015 22:37:05 +0000 (14:37 -0800)
committerJunio C Hamano <gitster@pobox.com>
Tue, 3 Mar 2015 22:37:06 +0000 (14:37 -0800)
The "interpolated-path" option of "git daemon" inserted any string
client declared on the "host=" capability request without checking.
Sanitize and limit %H and %CH to a saner and a valid DNS name.

* jk/daemon-interpolate:
daemon: sanitize incoming virtual hostname
t5570: test git-daemon's --interpolated-path option
git_connect: let user override virtual-host we send to daemon

connect.c
daemon.c
t/t5570-git-daemon.sh
index 062e133aa39b9e522bf3a03c5e57ac67c03ad88e..372ac5ad9d16b7bc3f7e03012656b1541af5ba4c 100644 (file)
--- 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
index 09fa652fd172cf7bb5b1cf8a4f0ccbc072ca2111..c3edd960ec5bf686b66fa55dcfea712e455fe772 100644 (file)
--- 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);
                }
index 6b163799510fa608fc6effa146accf295d0068f5..b7e283252d7d73e937ef87096801f2d252e95961 100755 (executable)
@@ -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