Sync with 2.3.10
authorJunio C Hamano <gitster@pobox.com>
Mon, 28 Sep 2015 22:28:26 +0000 (15:28 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 28 Sep 2015 22:28:31 +0000 (15:28 -0700)
27 files changed:
Documentation/RelNotes/2.3.10.txt [new file with mode: 0644]
Documentation/git.txt
builtin/blame.c
builtin/merge-file.c
builtin/merge-tree.c
builtin/rerere.c
combine-diff.c
connect.c
diff.c
diffcore-pickaxe.c
git-submodule.sh
http.c
line-log.c
ll-merge.c
t/lib-httpd/apache.conf
t/lib-proto-disable.sh [new file with mode: 0644]
t/t5810-proto-disable-local.sh [new file with mode: 0755]
t/t5811-proto-disable-git.sh [new file with mode: 0755]
t/t5812-proto-disable-http.sh [new file with mode: 0755]
t/t5813-proto-disable-ssh.sh [new file with mode: 0755]
t/t5814-proto-disable-ext.sh [new file with mode: 0755]
t/t5815-submodule-protos.sh [new file with mode: 0755]
transport-helper.c
transport.c
transport.h
xdiff-interface.c
xdiff-interface.h

diff --git a/Documentation/RelNotes/2.3.10.txt b/Documentation/RelNotes/2.3.10.txt
new file mode 100644 (file)
index 0000000..9d425d8
--- /dev/null
@@ -0,0 +1,18 @@
+Git v2.3.10 Release Notes
+=========================
+
+Fixes since v2.3.9
+------------------
+
+ * xdiff code we use to generate diffs is not prepared to handle
+   extremely large files.  It uses "int" in many places, which can
+   overflow if we have a very large number of lines or even bytes in
+   our input files, for example.  Cap the input size to soemwhere
+   around 1GB for now.
+
+ * Some protocols (like git-remote-ext) can execute arbitrary code
+   found in the URL.  The URLs that submodules use may come from
+   arbitrary sources (e.g., .gitmodules files in a remote
+   repository), and can hurt those who blindly enable recursive
+   fetch.  Restrict the allowed protocols to well known and safe
+   ones.
index 97d9fb4..5826bd9 100644 (file)
@@ -57,9 +57,10 @@ Documentation for older releases are available here:
   link:RelNotes/2.4.1.txt[2.4.1],
   link:RelNotes/2.4.0.txt[2.4].
 
-* link:v2.3.9/git.html[documentation for release 2.3.9]
+* link:v2.3.10/git.html[documentation for release 2.3.10]
 
 * release notes for
+  link:RelNotes/2.3.10.txt[2.3.10],
   link:RelNotes/2.3.9.txt[2.3.9],
   link:RelNotes/2.3.8.txt[2.3.8],
   link:RelNotes/2.3.7.txt[2.3.7],
@@ -1059,6 +1060,33 @@ GIT_ICASE_PATHSPECS::
        an operation has touched every ref (e.g., because you are
        cloning a repository to make a backup).
 
+`GIT_ALLOW_PROTOCOL`::
+       If set, provide a colon-separated list of protocols which are
+       allowed to be used with fetch/push/clone. This is useful to
+       restrict recursive submodule initialization from an untrusted
+       repository. Any protocol not mentioned will be disallowed (i.e.,
+       this is a whitelist, not a blacklist). If the variable is not
+       set at all, all protocols are enabled.  The protocol names
+       currently used by git are:
+
+         - `file`: any local file-based path (including `file://` URLs,
+           or local paths)
+
+         - `git`: the anonymous git protocol over a direct TCP
+           connection (or proxy, if configured)
+
+         - `ssh`: git over ssh (including `host:path` syntax,
+           `git+ssh://`, etc).
+
+         - `rsync`: git over rsync
+
+         - `http`: git over http, both "smart http" and "dumb http".
+           Note that this does _not_ include `https`; if you want both,
+           you should specify both as `http:https`.
+
+         - any external helpers are named by their protocol (e.g., use
+           `hg` to allow the `git-remote-hg` helper)
+
 
 Discussion[[Discussion]]
 ------------------------
index b3e948e..048ed53 100644 (file)
@@ -973,7 +973,10 @@ static void pass_blame_to_parent(struct scoreboard *sb,
        fill_origin_blob(&sb->revs->diffopt, target, &file_o);
        num_get_patch++;
 
-       diff_hunks(&file_p, &file_o, 0, blame_chunk_cb, &d);
+       if (diff_hunks(&file_p, &file_o, 0, blame_chunk_cb, &d))
+               die("unable to generate diff (%s -> %s)",
+                   sha1_to_hex(parent->commit->object.sha1),
+                   sha1_to_hex(target->commit->object.sha1));
        /* The rest are the same as the parent */
        blame_chunk(&d.dstq, &d.srcq, INT_MAX, d.offset, INT_MAX, parent);
        *d.dstq = NULL;
@@ -1119,7 +1122,9 @@ static void find_copy_in_blob(struct scoreboard *sb,
         * file_p partially may match that image.
         */
        memset(split, 0, sizeof(struct blame_entry [3]));
-       diff_hunks(file_p, &file_o, 1, handle_split_cb, &d);
+       if (diff_hunks(file_p, &file_o, 1, handle_split_cb, &d))
+               die("unable to generate diff (%s)",
+                   sha1_to_hex(parent->commit->object.sha1));
        /* remainder, if any, all match the preimage */
        handle_split(sb, ent, d.tlno, d.plno, ent->num_lines, parent, split);
 }
index ea8093f..50d0bc8 100644 (file)
@@ -75,7 +75,8 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix)
                        names[i] = argv[i];
                if (read_mmfile(mmfs + i, fname))
                        return -1;
-               if (buffer_is_binary(mmfs[i].ptr, mmfs[i].size))
+               if (mmfs[i].size > MAX_XDIFF_SIZE ||
+                   buffer_is_binary(mmfs[i].ptr, mmfs[i].size))
                        return error("Cannot merge binary files: %s",
                                        argv[i]);
        }
index f9ab485..2a4aafe 100644 (file)
@@ -118,7 +118,8 @@ static void show_diff(struct merge_list *entry)
        if (!dst.ptr)
                size = 0;
        dst.size = size;
-       xdi_diff(&src, &dst, &xpp, &xecfg, &ecb);
+       if (xdi_diff(&src, &dst, &xpp, &xecfg, &ecb))
+               die("unable to generate diff");
        free(src.ptr);
        free(dst.ptr);
 }
index 7afadd2..be55e0d 100644 (file)
@@ -29,9 +29,10 @@ static int diff_two(const char *file1, const char *label1,
        xdemitconf_t xecfg;
        xdemitcb_t ecb;
        mmfile_t minus, plus;
+       int ret;
 
        if (read_mmfile(&minus, file1) || read_mmfile(&plus, file2))
-               return 1;
+               return -1;
 
        printf("--- a/%s\n+++ b/%s\n", label1, label2);
        fflush(stdout);
@@ -40,11 +41,11 @@ static int diff_two(const char *file1, const char *label1,
        memset(&xecfg, 0, sizeof(xecfg));
        xecfg.ctxlen = 3;
        ecb.outf = outf;
-       xdi_diff(&minus, &plus, &xpp, &xecfg, &ecb);
+       ret = xdi_diff(&minus, &plus, &xpp, &xecfg, &ecb);
 
        free(minus.ptr);
        free(plus.ptr);
-       return 0;
+       return ret;
 }
 
 int cmd_rerere(int argc, const char **argv, const char *prefix)
@@ -104,7 +105,8 @@ int cmd_rerere(int argc, const char **argv, const char *prefix)
                for (i = 0; i < merge_rr.nr; i++) {
                        const char *path = merge_rr.items[i].string;
                        const char *name = (const char *)merge_rr.items[i].util;
-                       diff_two(rerere_path(name, "preimage"), path, path, path);
+                       if (diff_two(rerere_path(name, "preimage"), path, path, path))
+                               die("unable to generate diff for %s", name);
                }
        else
                usage_with_options(rerere_usage, options);
index d777e92..5cae5fb 100644 (file)
@@ -419,8 +419,10 @@ static void combine_diff(const unsigned char *parent, unsigned int mode,
        state.num_parent = num_parent;
        state.n = n;
 
-       xdi_diff_outf(&parent_file, result_file, consume_line, &state,
-                     &xpp, &xecfg);
+       if (xdi_diff_outf(&parent_file, result_file, consume_line, &state,
+                         &xpp, &xecfg))
+               die("unable to generate combined diff for %s",
+                   sha1_to_hex(parent));
        free(parent_file.ptr);
 
        /* Assign line numbers for this parent.
index c0144d8..27a706f 100644 (file)
--- a/connect.c
+++ b/connect.c
@@ -9,6 +9,7 @@
 #include "url.h"
 #include "string-list.h"
 #include "sha1-array.h"
+#include "transport.h"
 
 static char *server_capabilities;
 static const char *parse_feature_value(const char *, const char *, int *);
@@ -694,6 +695,8 @@ struct child_process *git_connect(int fd[2], const char *url,
                else
                        target_host = xstrdup(hostandport);
 
+               transport_check_allowed("git");
+
                /* These underlying connection commands die() if they
                 * cannot connect.
                 */
@@ -727,6 +730,7 @@ struct child_process *git_connect(int fd[2], const char *url,
                        int putty, tortoiseplink = 0;
                        char *ssh_host = hostandport;
                        const char *port = NULL;
+                       transport_check_allowed("ssh");
                        get_host_and_port(&ssh_host, &port);
 
                        if (!port)
@@ -781,6 +785,7 @@ struct child_process *git_connect(int fd[2], const char *url,
                        /* remove repo-local variables from the environment */
                        conn->env = local_repo_env;
                        conn->use_shell = 1;
+                       transport_check_allowed("file");
                }
                argv_array_push(&conn->args, cmd.buf);
 
diff --git a/diff.c b/diff.c
index 100773f..f62b7f7 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -1002,8 +1002,9 @@ static void diff_words_show(struct diff_words_data *diff_words)
        xpp.flags = 0;
        /* as only the hunk header will be parsed, we need a 0-context */
        xecfg.ctxlen = 0;
-       xdi_diff_outf(&minus, &plus, fn_out_diff_words_aux, diff_words,
-                     &xpp, &xecfg);
+       if (xdi_diff_outf(&minus, &plus, fn_out_diff_words_aux, diff_words,
+                         &xpp, &xecfg))
+               die("unable to generate word diff");
        free(minus.ptr);
        free(plus.ptr);
        if (diff_words->current_plus != diff_words->plus.text.ptr +
@@ -2400,8 +2401,9 @@ static void builtin_diff(const char *name_a,
                        xecfg.ctxlen = strtoul(v, NULL, 10);
                if (o->word_diff)
                        init_diff_words_data(&ecbdata, o, one, two);
-               xdi_diff_outf(&mf1, &mf2, fn_out_consume, &ecbdata,
-                             &xpp, &xecfg);
+               if (xdi_diff_outf(&mf1, &mf2, fn_out_consume, &ecbdata,
+                                 &xpp, &xecfg))
+                       die("unable to generate diff for %s", one->path);
                if (o->word_diff)
                        free_diff_words_data(&ecbdata);
                if (textconv_one)
@@ -2478,8 +2480,9 @@ static void builtin_diffstat(const char *name_a, const char *name_b,
                xpp.flags = o->xdl_opts;
                xecfg.ctxlen = o->context;
                xecfg.interhunkctxlen = o->interhunkcontext;
-               xdi_diff_outf(&mf1, &mf2, diffstat_consume, diffstat,
-                             &xpp, &xecfg);
+               if (xdi_diff_outf(&mf1, &mf2, diffstat_consume, diffstat,
+                                 &xpp, &xecfg))
+                       die("unable to generate diffstat for %s", one->path);
        }
 
        diff_free_filespec_data(one);
@@ -2525,8 +2528,9 @@ static void builtin_checkdiff(const char *name_a, const char *name_b,
                memset(&xecfg, 0, sizeof(xecfg));
                xecfg.ctxlen = 1; /* at least one context line */
                xpp.flags = 0;
-               xdi_diff_outf(&mf1, &mf2, checkdiff_consume, &data,
-                             &xpp, &xecfg);
+               if (xdi_diff_outf(&mf1, &mf2, checkdiff_consume, &data,
+                                 &xpp, &xecfg))
+                       die("unable to generate checkdiff for %s", one->path);
 
                if (data.ws_rule & WS_BLANK_AT_EOF) {
                        struct emit_callback ecbdata;
@@ -4425,8 +4429,10 @@ static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1)
                xpp.flags = 0;
                xecfg.ctxlen = 3;
                xecfg.flags = 0;
-               xdi_diff_outf(&mf1, &mf2, patch_id_consume, &data,
-                             &xpp, &xecfg);
+               if (xdi_diff_outf(&mf1, &mf2, patch_id_consume, &data,
+                                 &xpp, &xecfg))
+                       return error("unable to generate patch-id diff for %s",
+                                    p->one->path);
        }
 
        git_SHA1_Final(sha1, &ctx);
index 185f86b..7715c13 100644 (file)
@@ -62,8 +62,8 @@ static int diff_grep(mmfile_t *one, mmfile_t *two,
        ecbdata.hit = 0;
        xecfg.ctxlen = o->context;
        xecfg.interhunkctxlen = o->interhunkcontext;
-       xdi_diff_outf(one, two, diffgrep_consume, &ecbdata,
-                     &xpp, &xecfg);
+       if (xdi_diff_outf(one, two, diffgrep_consume, &ecbdata, &xpp, &xecfg))
+               return 0;
        return ecbdata.hit;
 }
 
index 36797c3..78c2740 100755 (executable)
@@ -22,6 +22,15 @@ require_work_tree
 wt_prefix=$(git rev-parse --show-prefix)
 cd_to_toplevel
 
+# Restrict ourselves to a vanilla subset of protocols; the URLs
+# we get are under control of a remote repository, and we do not
+# want them kicking off arbitrary git-remote-* programs.
+#
+# If the user has already specified a set of allowed protocols,
+# we assume they know what they're doing and use that instead.
+: ${GIT_ALLOW_PROTOCOL=file:git:http:https:ssh}
+export GIT_ALLOW_PROTOCOL
+
 command=
 branch=
 force=
diff --git a/http.c b/http.c
index 9a7e089..9448c50 100644 (file)
--- a/http.c
+++ b/http.c
@@ -9,6 +9,7 @@
 #include "version.h"
 #include "pkt-line.h"
 #include "gettext.h"
+#include "transport.h"
 
 int active_requests;
 int http_is_verbose;
@@ -337,6 +338,7 @@ static void set_curl_keepalive(CURL *c)
 static CURL *get_curl_handle(void)
 {
        CURL *result = curl_easy_init();
+       long allowed_protocols = 0;
 
        if (!result)
                die("curl_easy_init failed");
@@ -384,11 +386,27 @@ static CURL *get_curl_handle(void)
        }
 
        curl_easy_setopt(result, CURLOPT_FOLLOWLOCATION, 1);
+       curl_easy_setopt(result, CURLOPT_MAXREDIRS, 20);
 #if LIBCURL_VERSION_NUM >= 0x071301
        curl_easy_setopt(result, CURLOPT_POSTREDIR, CURL_REDIR_POST_ALL);
 #elif LIBCURL_VERSION_NUM >= 0x071101
        curl_easy_setopt(result, CURLOPT_POST301, 1);
 #endif
+#if LIBCURL_VERSION_NUM >= 0x071304
+       if (is_transport_allowed("http"))
+               allowed_protocols |= CURLPROTO_HTTP;
+       if (is_transport_allowed("https"))
+               allowed_protocols |= CURLPROTO_HTTPS;
+       if (is_transport_allowed("ftp"))
+               allowed_protocols |= CURLPROTO_FTP;
+       if (is_transport_allowed("ftps"))
+               allowed_protocols |= CURLPROTO_FTPS;
+       curl_easy_setopt(result, CURLOPT_REDIR_PROTOCOLS, allowed_protocols);
+#else
+       if (transport_restrict_protocols())
+               warning("protocol restrictions not applied to curl redirects because\n"
+                       "your curl version is too old (>= 7.19.4)");
+#endif
 
        if (getenv("GIT_CURL_VERBOSE"))
                curl_easy_setopt(result, CURLOPT_VERBOSE, 1);
index c12c69f..626b22c 100644 (file)
@@ -325,7 +325,7 @@ static int collect_diff_cb(long start_a, long count_a,
        return 0;
 }
 
-static void collect_diff(mmfile_t *parent, mmfile_t *target, struct diff_ranges *out)
+static int collect_diff(mmfile_t *parent, mmfile_t *target, struct diff_ranges *out)
 {
        struct collect_diff_cbdata cbdata = {NULL};
        xpparam_t xpp;
@@ -340,7 +340,7 @@ static void collect_diff(mmfile_t *parent, mmfile_t *target, struct diff_ranges
        xecfg.hunk_func = collect_diff_cb;
        memset(&ecb, 0, sizeof(ecb));
        ecb.priv = &cbdata;
-       xdi_diff(parent, target, &xpp, &xecfg, &ecb);
+       return xdi_diff(parent, target, &xpp, &xecfg, &ecb);
 }
 
 /*
@@ -1030,7 +1030,8 @@ static int process_diff_filepair(struct rev_info *rev,
        }
 
        diff_ranges_init(&diff);
-       collect_diff(&file_parent, &file_target, &diff);
+       if (collect_diff(&file_parent, &file_target, &diff))
+               die("unable to generate diff for %s", pair->one->path);
 
        /* NEEDSWORK should apply some heuristics to prevent mismatches */
        free(rg->path);
index 8ea03e5..4e789f5 100644 (file)
@@ -88,7 +88,10 @@ static int ll_xdl_merge(const struct ll_merge_driver *drv_unused,
        xmparam_t xmp;
        assert(opts);
 
-       if (buffer_is_binary(orig->ptr, orig->size) ||
+       if (orig->size > MAX_XDIFF_SIZE ||
+           src1->size > MAX_XDIFF_SIZE ||
+           src2->size > MAX_XDIFF_SIZE ||
+           buffer_is_binary(orig->ptr, orig->size) ||
            buffer_is_binary(src1->ptr, src1->size) ||
            buffer_is_binary(src2->ptr, src2->size)) {
                return ll_binary_merge(drv_unused, result,
index 0b81a00..7d15e6d 100644 (file)
@@ -119,6 +119,10 @@ RewriteRule ^/smart-redir-perm/(.*)$ /smart/$1 [R=301]
 RewriteRule ^/smart-redir-temp/(.*)$ /smart/$1 [R=302]
 RewriteRule ^/smart-redir-auth/(.*)$ /auth/smart/$1 [R=301]
 RewriteRule ^/smart-redir-limited/(.*)/info/refs$ /smart/$1/info/refs [R=301]
+RewriteRule ^/ftp-redir/(.*)$ ftp://localhost:1000/$1 [R=302]
+
+RewriteRule ^/loop-redir/x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-(.*) /$1 [R=302]
+RewriteRule ^/loop-redir/(.*)$ /loop-redir/x-$1 [R=302]
 
 <IfDefine SSL>
 LoadModule ssl_module modules/mod_ssl.so
diff --git a/t/lib-proto-disable.sh b/t/lib-proto-disable.sh
new file mode 100644 (file)
index 0000000..b0917d9
--- /dev/null
@@ -0,0 +1,96 @@
+# Test routines for checking protocol disabling.
+
+# test cloning a particular protocol
+#   $1 - description of the protocol
+#   $2 - machine-readable name of the protocol
+#   $3 - the URL to try cloning
+test_proto () {
+       desc=$1
+       proto=$2
+       url=$3
+
+       test_expect_success "clone $1 (enabled)" '
+               rm -rf tmp.git &&
+               (
+                       GIT_ALLOW_PROTOCOL=$proto &&
+                       export GIT_ALLOW_PROTOCOL &&
+                       git clone --bare "$url" tmp.git
+               )
+       '
+
+       test_expect_success "fetch $1 (enabled)" '
+               (
+                       cd tmp.git &&
+                       GIT_ALLOW_PROTOCOL=$proto &&
+                       export GIT_ALLOW_PROTOCOL &&
+                       git fetch
+               )
+       '
+
+       test_expect_success "push $1 (enabled)" '
+               (
+                       cd tmp.git &&
+                       GIT_ALLOW_PROTOCOL=$proto &&
+                       export GIT_ALLOW_PROTOCOL &&
+                       git push origin HEAD:pushed
+               )
+       '
+
+       test_expect_success "push $1 (disabled)" '
+               (
+                       cd tmp.git &&
+                       GIT_ALLOW_PROTOCOL=none &&
+                       export GIT_ALLOW_PROTOCOL &&
+                       test_must_fail git push origin HEAD:pushed
+               )
+       '
+
+       test_expect_success "fetch $1 (disabled)" '
+               (
+                       cd tmp.git &&
+                       GIT_ALLOW_PROTOCOL=none &&
+                       export GIT_ALLOW_PROTOCOL &&
+                       test_must_fail git fetch
+               )
+       '
+
+       test_expect_success "clone $1 (disabled)" '
+               rm -rf tmp.git &&
+               (
+                       GIT_ALLOW_PROTOCOL=none &&
+                       export GIT_ALLOW_PROTOCOL &&
+                       test_must_fail git clone --bare "$url" tmp.git
+               )
+       '
+}
+
+# set up an ssh wrapper that will access $host/$repo in the
+# trash directory, and enable it for subsequent tests.
+setup_ssh_wrapper () {
+       test_expect_success 'setup ssh wrapper' '
+               write_script ssh-wrapper <<-\EOF &&
+               echo >&2 "ssh: $*"
+               host=$1; shift
+               cd "$TRASH_DIRECTORY/$host" &&
+               eval "$*"
+               EOF
+               GIT_SSH="$PWD/ssh-wrapper" &&
+               export GIT_SSH &&
+               export TRASH_DIRECTORY
+       '
+}
+
+# set up a wrapper that can be used with remote-ext to
+# access repositories in the "remote" directory of trash-dir,
+# like "ext::fake-remote %S repo.git"
+setup_ext_wrapper () {
+       test_expect_success 'setup ext wrapper' '
+               write_script fake-remote <<-\EOF &&
+               echo >&2 "fake-remote: $*"
+               cd "$TRASH_DIRECTORY/remote" &&
+               eval "$*"
+               EOF
+               PATH=$TRASH_DIRECTORY:$PATH &&
+               export TRASH_DIRECTORY
+       '
+}
diff --git a/t/t5810-proto-disable-local.sh b/t/t5810-proto-disable-local.sh
new file mode 100755 (executable)
index 0000000..563592d
--- /dev/null
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+test_description='test disabling of local paths in clone/fetch'
+. ./test-lib.sh
+. "$TEST_DIRECTORY/lib-proto-disable.sh"
+
+test_expect_success 'setup repository to clone' '
+       test_commit one
+'
+
+test_proto "file://" file "file://$PWD"
+test_proto "path" file .
+
+test_done
diff --git a/t/t5811-proto-disable-git.sh b/t/t5811-proto-disable-git.sh
new file mode 100755 (executable)
index 0000000..8ac6b2a
--- /dev/null
@@ -0,0 +1,20 @@
+#!/bin/sh
+
+test_description='test disabling of git-over-tcp in clone/fetch'
+. ./test-lib.sh
+. "$TEST_DIRECTORY/lib-proto-disable.sh"
+. "$TEST_DIRECTORY/lib-git-daemon.sh"
+start_git_daemon
+
+test_expect_success 'create git-accessible repo' '
+       bare="$GIT_DAEMON_DOCUMENT_ROOT_PATH/repo.git" &&
+       test_commit one &&
+       git --bare init "$bare" &&
+       git push "$bare" HEAD &&
+       >"$bare/git-daemon-export-ok" &&
+       git -C "$bare" config daemon.receivepack true
+'
+
+test_proto "git://" git "$GIT_DAEMON_URL/repo.git"
+
+test_done
diff --git a/t/t5812-proto-disable-http.sh b/t/t5812-proto-disable-http.sh
new file mode 100755 (executable)
index 0000000..0d105d5
--- /dev/null
@@ -0,0 +1,33 @@
+#!/bin/sh
+
+test_description='test disabling of git-over-http in clone/fetch'
+. ./test-lib.sh
+. "$TEST_DIRECTORY/lib-proto-disable.sh"
+. "$TEST_DIRECTORY/lib-httpd.sh"
+start_httpd
+
+test_expect_success 'create git-accessible repo' '
+       bare="$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
+       test_commit one &&
+       git --bare init "$bare" &&
+       git push "$bare" HEAD &&
+       git -C "$bare" config http.receivepack true
+'
+
+test_proto "smart http" http "$HTTPD_URL/smart/repo.git"
+
+test_expect_success 'curl redirects respect whitelist' '
+       test_must_fail env GIT_ALLOW_PROTOCOL=http:https \
+               git clone "$HTTPD_URL/ftp-redir/repo.git" 2>stderr &&
+       {
+               test_i18ngrep "ftp.*disabled" stderr ||
+               test_i18ngrep "your curl version is too old"
+       }
+'
+
+test_expect_success 'curl limits redirects' '
+       test_must_fail git clone "$HTTPD_URL/loop-redir/smart/repo.git"
+'
+
+stop_httpd
+test_done
diff --git a/t/t5813-proto-disable-ssh.sh b/t/t5813-proto-disable-ssh.sh
new file mode 100755 (executable)
index 0000000..ad877d7
--- /dev/null
@@ -0,0 +1,20 @@
+#!/bin/sh
+
+test_description='test disabling of git-over-ssh in clone/fetch'
+. ./test-lib.sh
+. "$TEST_DIRECTORY/lib-proto-disable.sh"
+
+setup_ssh_wrapper
+
+test_expect_success 'setup repository to clone' '
+       test_commit one &&
+       mkdir remote &&
+       git init --bare remote/repo.git &&
+       git push remote/repo.git HEAD
+'
+
+test_proto "host:path" ssh "remote:repo.git"
+test_proto "ssh://" ssh "ssh://remote/$PWD/remote/repo.git"
+test_proto "git+ssh://" ssh "git+ssh://remote/$PWD/remote/repo.git"
+
+test_done
diff --git a/t/t5814-proto-disable-ext.sh b/t/t5814-proto-disable-ext.sh
new file mode 100755 (executable)
index 0000000..9d6f7df
--- /dev/null
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+test_description='test disabling of remote-helper paths in clone/fetch'
+. ./test-lib.sh
+. "$TEST_DIRECTORY/lib-proto-disable.sh"
+
+setup_ext_wrapper
+
+test_expect_success 'setup repository to clone' '
+       test_commit one &&
+       mkdir remote &&
+       git init --bare remote/repo.git &&
+       git push remote/repo.git HEAD
+'
+
+test_proto "remote-helper" ext "ext::fake-remote %S repo.git"
+
+test_done
diff --git a/t/t5815-submodule-protos.sh b/t/t5815-submodule-protos.sh
new file mode 100755 (executable)
index 0000000..06f55a1
--- /dev/null
@@ -0,0 +1,43 @@
+#!/bin/sh
+
+test_description='test protocol whitelisting with submodules'
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-proto-disable.sh
+
+setup_ext_wrapper
+setup_ssh_wrapper
+
+test_expect_success 'setup repository with submodules' '
+       mkdir remote &&
+       git init remote/repo.git &&
+       (cd remote/repo.git && test_commit one) &&
+       # submodule-add should probably trust what we feed it on the cmdline,
+       # but its implementation is overly conservative.
+       GIT_ALLOW_PROTOCOL=ssh git submodule add remote:repo.git ssh-module &&
+       GIT_ALLOW_PROTOCOL=ext git submodule add "ext::fake-remote %S repo.git" ext-module &&
+       git commit -m "add submodules"
+'
+
+test_expect_success 'clone with recurse-submodules fails' '
+       test_must_fail git clone --recurse-submodules . dst
+'
+
+test_expect_success 'setup individual updates' '
+       rm -rf dst &&
+       git clone . dst &&
+       git -C dst submodule init
+'
+
+test_expect_success 'update of ssh allowed' '
+       git -C dst submodule update ssh-module
+'
+
+test_expect_success 'update of ext not allowed' '
+       test_must_fail git -C dst submodule update ext-module
+'
+
+test_expect_success 'user can override whitelist' '
+       GIT_ALLOW_PROTOCOL=ext git -C dst submodule update ext-module
+'
+
+test_done
index 5d99a6b..b486441 100644 (file)
@@ -1039,6 +1039,8 @@ int transport_helper_init(struct transport *transport, const char *name)
        struct helper_data *data = xcalloc(1, sizeof(*data));
        data->name = name;
 
+       transport_check_allowed(name);
+
        if (getenv("GIT_TRANSPORT_HELPER_DEBUG"))
                debug = 1;
 
index eca9b8c..198502d 100644 (file)
@@ -914,6 +914,42 @@ static int external_specification_len(const char *url)
        return strchr(url, ':') - url;
 }
 
+static const struct string_list *protocol_whitelist(void)
+{
+       static int enabled = -1;
+       static struct string_list allowed = STRING_LIST_INIT_DUP;
+
+       if (enabled < 0) {
+               const char *v = getenv("GIT_ALLOW_PROTOCOL");
+               if (v) {
+                       string_list_split(&allowed, v, ':', -1);
+                       string_list_sort(&allowed);
+                       enabled = 1;
+               } else {
+                       enabled = 0;
+               }
+       }
+
+       return enabled ? &allowed : NULL;
+}
+
+int is_transport_allowed(const char *type)
+{
+       const struct string_list *allowed = protocol_whitelist();
+       return !allowed || string_list_has_string(allowed, type);
+}
+
+void transport_check_allowed(const char *type)
+{
+       if (!is_transport_allowed(type))
+               die("transport '%s' not allowed", type);
+}
+
+int transport_restrict_protocols(void)
+{
+       return !!protocol_whitelist();
+}
+
 struct transport *transport_get(struct remote *remote, const char *url)
 {
        const char *helper;
@@ -945,12 +981,14 @@ struct transport *transport_get(struct remote *remote, const char *url)
        if (helper) {
                transport_helper_init(ret, helper);
        } else if (starts_with(url, "rsync:")) {
+               transport_check_allowed("rsync");
                ret->get_refs_list = get_refs_via_rsync;
                ret->fetch = fetch_objs_via_rsync;
                ret->push = rsync_transport_push;
                ret->smart_options = NULL;
        } else if (url_is_local_not_ssh(url) && is_file(url) && is_bundle(url, 1)) {
                struct bundle_transport_data *data = xcalloc(1, sizeof(*data));
+               transport_check_allowed("file");
                ret->data = data;
                ret->get_refs_list = get_refs_from_bundle;
                ret->fetch = fetch_refs_from_bundle;
@@ -962,7 +1000,10 @@ struct transport *transport_get(struct remote *remote, const char *url)
                || starts_with(url, "ssh://")
                || starts_with(url, "git+ssh://")
                || starts_with(url, "ssh+git://")) {
-               /* These are builtin smart transports. */
+               /*
+                * These are builtin smart transports; "allowed" transports
+                * will be checked individually in git_connect.
+                */
                struct git_transport_data *data = xcalloc(1, sizeof(*data));
                ret->data = data;
                ret->set_option = NULL;
index 18d2cf8..9770777 100644 (file)
@@ -133,6 +133,24 @@ struct transport {
 /* Returns a transport suitable for the url */
 struct transport *transport_get(struct remote *, const char *);
 
+/*
+ * Check whether a transport is allowed by the environment. Type should
+ * generally be the URL scheme, as described in Documentation/git.txt
+ */
+int is_transport_allowed(const char *type);
+
+/*
+ * Check whether a transport is allowed by the environment,
+ * and die otherwise.
+ */
+void transport_check_allowed(const char *type);
+
+/*
+ * Returns true if the user has attempted to turn on protocol
+ * restrictions at all.
+ */
+int transport_restrict_protocols(void);
+
 /* Transport options which apply to git:// and scp-style URLs */
 
 /* The program to use on the remote side to send a pack */
index ecfa05f..cb67c1c 100644 (file)
@@ -131,6 +131,9 @@ int xdi_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, xdemitconf_t co
        mmfile_t a = *mf1;
        mmfile_t b = *mf2;
 
+       if (mf1->size > MAX_XDIFF_SIZE || mf2->size > MAX_XDIFF_SIZE)
+               return -1;
+
        trim_common_tail(&a, &b, xecfg->ctxlen);
 
        return xdl_diff(&a, &b, xpp, xecfg, xecb);
index eff7762..fbb5a1c 100644 (file)
@@ -3,6 +3,13 @@
 
 #include "xdiff/xdiff.h"
 
+/*
+ * xdiff isn't equipped to handle content over a gigabyte;
+ * we make the cutoff 1GB - 1MB to give some breathing
+ * room for constant-sized additions (e.g., merge markers)
+ */
+#define MAX_XDIFF_SIZE (1024UL * 1024 * 1023)
+
 typedef void (*xdiff_emit_consume_fn)(void *, char *, unsigned long);
 
 int xdi_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, xdemitconf_t const *xecfg, xdemitcb_t *ecb);