diff --git a/0001-Drop-DESTDIR-from-python-instlibdir.patch b/0001-Drop-DESTDIR-from-python-instlibdir.patch deleted file mode 100644 index b6e7120432709a5730a653d8e5b4435a6c7d6775..0000000000000000000000000000000000000000 --- a/0001-Drop-DESTDIR-from-python-instlibdir.patch +++ /dev/null @@ -1,29 +0,0 @@ -From d40d33173dc24d9b7ad6f5071994f90b5f9a71e8 Mon Sep 17 00:00:00 2001 -From: Todd Zullinger -Date: Wed, 27 Mar 2013 14:01:57 -0400 -Subject: [PATCH] Drop DESTDIR from python instlibdir - -When building packages, we install to DESTDIR but we don't want this to -end up hard-coded in the scripts. - -This needs discussed upstream to find a proper solution. ---- - git_remote_helpers/Makefile | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/git_remote_helpers/Makefile b/git_remote_helpers/Makefile -index 3d12232..36d40b5 100644 ---- a/git_remote_helpers/Makefile -+++ b/git_remote_helpers/Makefile -@@ -38,7 +38,7 @@ install: $(pysetupfile) - $(PYTHON_PATH) $(pysetupfile) install --prefix $(DESTDIR_SQ)$(prefix) - - instlibdir: $(pysetupfile) -- @echo "$(DESTDIR_SQ)$(prefix)/$(PYLIBDIR)" -+ @echo "$(prefix)/$(PYLIBDIR)" - - clean: - $(QUIET)$(PYTHON_PATH) $(pysetupfile) $(QUIETSETUP) clean -a --- -1.8.1 - diff --git a/0001-Fix-CVE-2016-2315-CVE-2016-2324.patch b/0001-Fix-CVE-2016-2315-CVE-2016-2324.patch deleted file mode 100644 index 0c3aaab52b81830f072c495c88279f2c31d6c897..0000000000000000000000000000000000000000 --- a/0001-Fix-CVE-2016-2315-CVE-2016-2324.patch +++ /dev/null @@ -1,117 +0,0 @@ -From 73b65b02d5d1aa4612bb7015f80c8cd1b5e828cd Mon Sep 17 00:00:00 2001 -From: Petr Stodulka -Date: Fri, 18 Mar 2016 17:14:32 +0100 -Subject: [PATCH] Fix CVE-2016-2315 CVE-2016-2324 - -- added upstream macros for detecting size_t overflow (much more just - for easier related changes in future, if we want to do some yet) -- upstream solution removes function path_name() and modify all related - part of code to replace this function. However, it's too hard for - backport to such old version of git without unchanged behaviour, - so application just die with error message instead. ---- - diff.h | 4 ++-- - git-compat-util.h | 35 +++++++++++++++++++++++++++++++++++ - revision.c | 13 ++++++++++--- - 3 files changed, 47 insertions(+), 5 deletions(-) - -diff --git a/diff.h b/diff.h -index ce123fa..88db230 100644 ---- a/diff.h -+++ b/diff.h -@@ -209,8 +209,8 @@ struct combine_diff_path { - } parent[FLEX_ARRAY]; - }; - #define combine_diff_path_size(n, l) \ -- (sizeof(struct combine_diff_path) + \ -- sizeof(struct combine_diff_parent) * (n) + (l) + 1) -+ st_add4(sizeof(struct combine_diff_path), (l), 1, \ -+ st_mult(sizeof(struct combine_diff_parent), (n))) - - extern void show_combined_diff(struct combine_diff_path *elem, int num_parent, - int dense, struct rev_info *); -diff --git a/git-compat-util.h b/git-compat-util.h -index d493a8c..d5a15fa 100644 ---- a/git-compat-util.h -+++ b/git-compat-util.h -@@ -46,6 +46,14 @@ - #define unsigned_add_overflows(a, b) \ - ((b) > maximum_unsigned_value_of_type(a) - (a)) - -+/* -+ * Returns true if the multiplication of "a" and "b" will -+ * overflow. The types of "a" and "b" must match and must be unsigned. -+ * Note that this macro evaluates "a" twice! -+ */ -+#define unsigned_mult_overflows(a, b) \ -+ ((a) && (b) > maximum_unsigned_value_of_type(a) / (a)) -+ - #ifdef __GNUC__ - #define TYPEOF(x) (__typeof__(x)) - #else -@@ -526,6 +534,33 @@ extern void release_pack_memory(size_t); - typedef void (*try_to_free_t)(size_t); - extern try_to_free_t set_try_to_free_routine(try_to_free_t); - -+static inline size_t st_add(size_t a, size_t b) -+{ -+ if (unsigned_add_overflows(a, b)) -+ die("size_t overflow: %"PRIuMAX" + %"PRIuMAX, -+ (uintmax_t)a, (uintmax_t)b); -+ return a + b; -+} -+#define st_add3(a,b,c) st_add((a),st_add((b),(c))) -+#define st_add4(a,b,c,d) st_add((a),st_add3((b),(c),(d))) -+ -+static inline size_t st_mult(size_t a, size_t b) -+{ -+ if (unsigned_mult_overflows(a, b)) -+ die("size_t overflow: %"PRIuMAX" * %"PRIuMAX, -+ (uintmax_t)a, (uintmax_t)b); -+ return a * b; -+} -+ -+static inline size_t st_sub(size_t a, size_t b) -+{ -+ if (a < b) -+ die("size_t underflow: %"PRIuMAX" - %"PRIuMAX, -+ (uintmax_t)a, (uintmax_t)b); -+ return a - b; -+} -+ -+ - extern char *xstrdup(const char *str); - extern void *xmalloc(size_t size); - extern void *xmallocz(size_t size); -diff --git a/revision.c b/revision.c -index f40ccf1..b897ca6 100644 ---- a/revision.c -+++ b/revision.c -@@ -24,16 +24,21 @@ char *path_name(const struct name_path *path, const char *name) - { - const struct name_path *p; - char *n, *m; -- int nlen = strlen(name); -- int len = nlen + 1; -+ size_t nlen = strlen(name); -+ size_t len = st_add(nlen, 1); -+ -+ if(len >= INT_MAX) -+ die("path_name(): path is too long."); - - for (p = path; p; p = p->up) { - if (p->elem_len) - len += p->elem_len + 1; -+ if(len >= INT_MAX) -+ die("path_name(): path is too long."); - } - n = xmalloc(len); - m = n + len - (nlen + 1); -- strcpy(m, name); -+ memcpy(m, name, nlen + 1); - for (p = path; p; p = p->up) { - if (p->elem_len) { - m -= p->elem_len + 1; --- -2.4.3 - diff --git a/0001-Switch-git-instaweb-default-to-apache.patch b/0001-Switch-git-instaweb-default-to-apache.patch deleted file mode 100644 index 85717e5c315b6e51dac0ae2d89c38bbf09b16073..0000000000000000000000000000000000000000 --- a/0001-Switch-git-instaweb-default-to-apache.patch +++ /dev/null @@ -1,76 +0,0 @@ -From fa378a40d41cbffed64b8b85394cb76c6303ef64 Mon Sep 17 00:00:00 2001 -From: Sebastian Kisela -Date: Tue, 26 Jun 2018 23:39:37 +0200 -Subject: [PATCH] Switch git instaweb default to apache - -On Fedora-derived systems, the apache httpd package installs modules -under /usr/lib{,64}/httpd/modules, depending on whether the system is -32- or 64-bit. A symlink from /etc/httpd/modules is created which -points to the proper module path. Use it to support apache on Fedora, -CentOS, and Red Hat systems. - -References upstream commit: 1976311aa285549599e5a451d7ad72b55a2b60e2 ---- - git-instaweb.sh | 32 +++++++++++++++++++++++--------- - 1 file changed, 23 insertions(+), 9 deletions(-) - -diff --git a/git-instaweb.sh b/git-instaweb.sh -index 01a1b05..06380b6 100755 ---- a/git-instaweb.sh -+++ b/git-instaweb.sh -@@ -34,7 +34,7 @@ conf="$GIT_DIR/gitweb/httpd.conf" - # Defaults: - - # if installed, it doesn't need further configuration (module_path) --test -z "$httpd" && httpd='lighttpd -f' -+test -z "$httpd" && httpd='httpd -f' - - # Default is @@GITWEBDIR@@ - test -z "$root" && root='@@GITWEBDIR@@' -@@ -324,13 +324,17 @@ EOF - } - - apache2_conf () { -- if test -z "$module_path" -- then -- test -d "/usr/lib/httpd/modules" && -- module_path="/usr/lib/httpd/modules" -- test -d "/usr/lib/apache2/modules" && -- module_path="/usr/lib/apache2/modules" -- fi -+ for candidate in \ -+ /etc/httpd \ -+ /usr/lib/apache2 \ -+ /usr/lib/httpd ; -+ do -+ if test -d "$candidate/modules" -+ then -+ module_path="$candidate/modules" -+ break -+ fi -+ done - bind= - test x"$local" = xtrue && bind='127.0.0.1:' - echo 'text/css css' > "$fqgitdir/mime.types" -@@ -344,7 +348,17 @@ PidFile "$fqgitdir/pid" - Listen $bind$port - EOF - -- for mod in mime dir env log_config -+ for mod in mpm_event mpm_prefork mpm_worker -+ do -+ if test -e $module_path/mod_${mod}.so -+ then -+ echo "LoadModule ${mod}_module " \ -+ "$module_path/mod_${mod}.so" >> "$conf" -+ # only one mpm module permitted -+ break -+ fi -+ done -+ for mod in mime dir env log_config authz_core unixd - do - if test -e $module_path/mod_${mod}.so - then --- -2.14.4 - diff --git a/0001-git-subtree-Use-gitexecdir-instead-of-libexecdir.patch b/0001-git-subtree-Use-gitexecdir-instead-of-libexecdir.patch deleted file mode 100644 index 73ae548eacb504505df4d64fcbd9cc9df124e19e..0000000000000000000000000000000000000000 --- a/0001-git-subtree-Use-gitexecdir-instead-of-libexecdir.patch +++ /dev/null @@ -1,42 +0,0 @@ -From 86c3e2b5188579bff1ff981910462ad5e563044b Mon Sep 17 00:00:00 2001 -From: Todd Zullinger -Date: Fri, 4 Jan 2013 11:54:21 -0500 -Subject: [PATCH] git-subtree: Use gitexecdir instead of libexecdir - -When the git subtree Makefile includes config.mak from the toplevel, -it's useful to have the same variables set globally applied. Using -gitexecdir instead of libexecdir respects the global settings more -consistently. - -Remove the unused gitdir variable as well. ---- - contrib/subtree/Makefile | 5 ++--- - 1 files changed, 2 insertions(+), 3 deletions(-) - -diff --git a/contrib/subtree/Makefile b/contrib/subtree/Makefile -index 36ae3e4..f87b945 100644 ---- a/contrib/subtree/Makefile -+++ b/contrib/subtree/Makefile -@@ -2,9 +2,8 @@ - -include ../../config.mak - - prefix ?= /usr/local -+gitexecdir ?= $(prefix)/libexec/git-core - mandir ?= $(prefix)/share/man --libexecdir ?= $(prefix)/libexec/git-core --gitdir ?= $(shell git --exec-path) - man1dir ?= $(mandir)/man1 - - gitver ?= $(word 3,$(shell git --version)) -@@ -30,7 +29,7 @@ $(GIT_SUBTREE): $(GIT_SUBTREE_SH) - doc: $(GIT_SUBTREE_DOC) - - install: $(GIT_SUBTREE) -- $(INSTALL) -m 755 $(GIT_SUBTREE) $(DESTDIR)$(libexecdir) -+ $(INSTALL) -m 755 $(GIT_SUBTREE) $(DESTDIR)$(gitexecdir) - - install-doc: install-man - --- -1.7.6 - diff --git a/0001-http-control-GSSAPI-credential-delegation.patch b/0001-http-control-GSSAPI-credential-delegation.patch deleted file mode 100644 index ccec8964bd00b9fc29d1b8791c2db3924c43e4a8..0000000000000000000000000000000000000000 --- a/0001-http-control-GSSAPI-credential-delegation.patch +++ /dev/null @@ -1,90 +0,0 @@ -From 7dbd01e4815727ce46de0b5d6c2916fec9154196 Mon Sep 17 00:00:00 2001 -From: Petr Stodulka -Date: Mon, 5 Dec 2016 16:49:09 +0100 -Subject: [PATCH] http: control GSSAPI credential delegation - -Delegation of credentials is disabled by default in libcurl since -version 7.21.7 due to security vulnerability CVE-2011-2192. Which -makes troubles with GSS/kerberos authentication when delegation -of credentials is required. This can be changed with option -CURLOPT_GSSAPI_DELEGATION in libcurl with set expected parameter -since libcurl version 7.22.0. - -This patch provides new configuration variable http.delegation -which corresponds to curl parameter "--delegation" (see man 1 curl). - -The following values are supported: - -* none (default). -* policy -* always ---- - http.c | 38 ++++++++++++++++++++++++++++++++++++++ - 1 file changed, 38 insertions(+) - -diff --git a/http.c b/http.c -index a1c7dcb..e7c77c0 100644 ---- a/http.c -+++ b/http.c -@@ -66,6 +66,19 @@ static struct curl_slist *no_pragma_header; - - static struct active_request_slot *active_queue_head; - -+#if LIBCURL_VERSION_NUM >= 0x071600 -+static const char *curl_deleg; -+static struct { -+ const char *name; -+ long curl_deleg_param; -+} curl_deleg_levels[] = { -+ { "none", CURLGSSAPI_DELEGATION_NONE }, -+ { "policy", CURLGSSAPI_DELEGATION_POLICY_FLAG }, -+ { "always", CURLGSSAPI_DELEGATION_FLAG }, -+}; -+#endif -+ -+ - size_t fread_buffer(char *ptr, size_t eltsize, size_t nmemb, void *buffer_) - { - size_t size = eltsize * nmemb; -@@ -169,6 +182,16 @@ static int http_options(const char *var, const char *value, void *cb) - curl_ssl_try = git_config_bool(var, value); - return 0; - } -+ -+ if (!strcmp("http.delegation", var)) { -+#if LIBCURL_VERSION_NUM >= 0x071600 -+ return git_config_string(&curl_deleg, var, value); -+#else -+ warning("Delegation control is not supported with cURL < 7.22.0"); -+ return 0; -+#endif -+ } -+ - if (!strcmp("http.minsessions", var)) { - min_curl_sessions = git_config_int(var, value); - #ifndef USE_CURL_MULTI -@@ -271,6 +294,21 @@ static CURL *get_curl_handle(void) - #ifdef LIBCURL_CAN_HANDLE_AUTH_ANY - curl_easy_setopt(result, CURLOPT_HTTPAUTH, CURLAUTH_ANY); - #endif -+#if LIBCURL_VERSION_NUM >= 0x071600 -+ if (curl_deleg) { -+ int i; -+ for (i = 0; i < ARRAY_SIZE(curl_deleg_levels); i++) { -+ if (!strcmp(curl_deleg, curl_deleg_levels[i].name)) { -+ curl_easy_setopt(result, CURLOPT_GSSAPI_DELEGATION, -+ curl_deleg_levels[i].curl_deleg_param); -+ break; -+ } -+ } -+ if (i == ARRAY_SIZE(curl_deleg_levels)) -+ warning("Unknown delegation method '%s': using default", -+ curl_deleg); -+ } -+#endif - - if (http_proactive_auth) - init_curl_http_auth(result); --- -2.5.5 - diff --git a/0001-submodule-allow-only-certain-protocols-for-submodule.patch b/0001-submodule-allow-only-certain-protocols-for-submodule.patch deleted file mode 100644 index e2067b19c20adf93dbd1b98785361cbed8482a0e..0000000000000000000000000000000000000000 --- a/0001-submodule-allow-only-certain-protocols-for-submodule.patch +++ /dev/null @@ -1,104 +0,0 @@ -From 6d69680505dbbc484178105815ed624fab40b2de Mon Sep 17 00:00:00 2001 -From: Petr Stodulka -Date: Wed, 28 Oct 2015 15:03:01 +0100 -Subject: [PATCH 1/5] submodule: allow only certain protocols for submodule - fetches - -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). Let's restrict submodules to fetching from a -known-good subset of protocols. - -Note that we apply this restriction to all submodule -commands, whether the URL comes from .gitmodules or not. -This is more restrictive than we need to be; for example, in -the tests we run: - - git submodule add ext::... - -which should be trusted, as the URL comes directly from the -command line provided by the user. But doing it this way is -simpler, and makes it much less likely that we would miss a -case. And since such protocols should be an exception -(especially because nobody who clones from them will be able -to update the submodules!), it's not likely to inconvenience -anyone in practice. ---- - git-submodule.sh | 9 +++++++++ - t/t5815-submodule-protos-sh | 43 +++++++++++++++++++++++++++++++++++++++++++ - 2 files changed, 52 insertions(+) - create mode 100644 t/t5815-submodule-protos-sh - -diff --git a/git-submodule.sh b/git-submodule.sh -index 79bfaac..bec3362 100755 ---- a/git-submodule.sh -+++ b/git-submodule.sh -@@ -19,6 +19,15 @@ OPTIONS_SPEC= - . git-parse-remote - require_work_tree - -+# 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/t/t5815-submodule-protos-sh b/t/t5815-submodule-protos-sh -new file mode 100644 -index 0000000..06f55a1 ---- /dev/null -+++ b/t/t5815-submodule-protos-sh -@@ -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 --- -2.1.0 - diff --git a/0002-transport-add-a-protocol-whitelist-environment-varia.patch b/0002-transport-add-a-protocol-whitelist-environment-varia.patch deleted file mode 100644 index d9087390186c12fbea119e5f3fb695bb0f75ec49..0000000000000000000000000000000000000000 --- a/0002-transport-add-a-protocol-whitelist-environment-varia.patch +++ /dev/null @@ -1,207 +0,0 @@ -From cfa4e13f09d07f679ffacdddfbe0ef44d1de32d9 Mon Sep 17 00:00:00 2001 -From: Petr Stodulka -Date: Wed, 28 Oct 2015 15:21:08 +0100 -Subject: [PATCH 2/5] transport: add a protocol-whitelist environment variable - -If we are cloning an untrusted remote repository into a -sandbox, we may also want to fetch remote submodules in -order to get the complete view as intended by the other -side. However, that opens us up to attacks where a malicious -user gets us to clone something they would not otherwise -have access to (this is not necessarily a problem by itself, -but we may then act on the cloned contents in a way that -exposes them to the attacker). - -Ideally such a setup would sandbox git entirely away from -high-value items, but this is not always practical or easy -to set up (e.g., OS network controls may block multiple -protocols, and we would want to enable some but not others). - -We can help this case by providing a way to restrict -particular protocols. We use a whitelist in the environment. -This is more annoying to set up than a blacklist, but -defaults to safety if the set of protocols git supports -grows). If no whitelist is specified, we continue to default -to allowing all protocols (this is an "unsafe" default, but -since the minority of users will want this sandboxing -effect, it is the only sensible one). - -A note on the tests: ideally these would all be in a single -test file, but the git-daemon and httpd test infrastructure -is an all-or-nothing proposition rather than a test-by-test -prerequisite. By putting them all together, we would be -unable to test the file-local code on machines without -apache. ---- - Documentation/git.txt | 32 ++++++++++++++++++++++++++++++++ - connect.c | 4 ++++ - transport-helper.c | 2 ++ - transport.c | 21 ++++++++++++++++++++- - transport.h | 7 +++++++ - 5 files changed, 65 insertions(+), 1 deletion(-) - -diff --git a/Documentation/git.txt b/Documentation/git.txt -index 443d88f..179a0e8 100644 ---- a/Documentation/git.txt -+++ b/Documentation/git.txt -@@ -847,6 +847,38 @@ GIT_LITERAL_PATHSPECS:: - literal paths to Git (e.g., paths previously given to you by - `git ls-tree`, `--raw` diff output, etc). - -+`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) -++ -+Note that this controls only git's internal protocol selection. -+If libcurl is used (e.g., by the `http` transport), it may -+redirect to other protocols. There is not currently any way to -+restrict this. -+ - - Discussion[[Discussion]] - ------------------------ -diff --git a/connect.c b/connect.c -index f57efd0..6d4ea13 100644 ---- a/connect.c -+++ b/connect.c -@@ -6,6 +6,7 @@ - #include "run-command.h" - #include "remote.h" - #include "url.h" -+#include "transport.h" - - static char *server_capabilities; - -@@ -587,6 +588,7 @@ struct child_process *git_connect(int fd[2], const char *url_orig, - * cannot connect. - */ - char *target_host = xstrdup(host); -+ transport_check_allowed("git"); - if (git_use_proxy(host)) - conn = git_proxy_connect(fd, host); - else -@@ -623,6 +625,7 @@ struct child_process *git_connect(int fd[2], const char *url_orig, - if (protocol == PROTO_SSH) { - const char *ssh = getenv("GIT_SSH"); - int putty = ssh && strcasestr(ssh, "plink"); -+ transport_check_allowed("ssh"); - if (!ssh) ssh = "ssh"; - - *arg++ = ssh; -@@ -639,6 +642,7 @@ struct child_process *git_connect(int fd[2], const char *url_orig, - /* remove repo-local variables from the environment */ - conn->env = local_repo_env; - conn->use_shell = 1; -+ transport_check_allowed("file"); - } - *arg++ = cmd.buf; - *arg = NULL; -diff --git a/transport-helper.c b/transport-helper.c -index 522d791..be8402a 100644 ---- a/transport-helper.c -+++ b/transport-helper.c -@@ -932,6 +932,8 @@ int transport_helper_init(struct transport *transport, const char *name) - struct helper_data *data = xcalloc(sizeof(*data), 1); - data->name = name; - -+ transport_check_allowed(name); -+ - if (getenv("GIT_TRANSPORT_HELPER_DEBUG")) - debug = 1; - -diff --git a/transport.c b/transport.c -index ba5d8af..733717d 100644 ---- a/transport.c -+++ b/transport.c -@@ -894,6 +894,20 @@ static int external_specification_len(const char *url) - return strchr(url, ':') - url; - } - -+void transport_check_allowed(const char *type) -+{ -+ struct string_list allowed = STRING_LIST_INIT_DUP; -+ const char *v = getenv("GIT_ALLOW_PROTOCOL"); -+ -+ if (!v) -+ return; -+ -+ string_list_split(&allowed, v, ':', -1); -+ if (!unsorted_string_list_has_string(&allowed, type)) -+ die("transport '%s' not allowed", type); -+ string_list_clear(&allowed, 0); -+} -+ - struct transport *transport_get(struct remote *remote, const char *url) - { - const char *helper; -@@ -925,12 +939,14 @@ struct transport *transport_get(struct remote *remote, const char *url) - if (helper) { - transport_helper_init(ret, helper); - } else if (!prefixcmp(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 (is_local(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; -@@ -942,7 +958,10 @@ struct transport *transport_get(struct remote *remote, const char *url) - || !prefixcmp(url, "ssh://") - || !prefixcmp(url, "git+ssh://") - || !prefixcmp(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; -diff --git a/transport.h b/transport.h -index fcb1d25..2beda7d 100644 ---- a/transport.h -+++ b/transport.h -@@ -113,6 +113,13 @@ 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, -+ * and die otherwise. type should generally be the URL scheme, -+ * as described in Documentation/git.txt -+ */ -+void transport_check_allowed(const char *type); -+ - /* Transport options which apply to git:// and scp-style URLs */ - - /* The program to use on the remote side to send a pack */ --- -2.1.0 - diff --git a/0003-transport-refactor-protocol-whitelist-code.patch b/0003-transport-refactor-protocol-whitelist-code.patch deleted file mode 100644 index ff5416d5587f4337d0e9501cd222dd422187eaa7..0000000000000000000000000000000000000000 --- a/0003-transport-refactor-protocol-whitelist-code.patch +++ /dev/null @@ -1,107 +0,0 @@ -From 9b9aabe6ab5d07227c1c02781f03a3c38fbc27b0 Mon Sep 17 00:00:00 2001 -From: Jeff King -Date: Tue, 22 Sep 2015 18:03:49 -0400 -Subject: [PATCH 3/5] transport: refactor protocol whitelist code - -The current callers only want to die when their transport is -prohibited. But future callers want to query the mechanism -without dying. - -Let's break out a few query functions, and also save the -results in a static list so we don't have to re-parse for -each query. - -Based-on-a-patch-by: Blake Burkhart -Signed-off-by: Jeff King -Signed-off-by: Junio C Hamano ---- - transport.c | 38 ++++++++++++++++++++++++++++++-------- - transport.h | 15 +++++++++++++-- - 2 files changed, 43 insertions(+), 10 deletions(-) - -diff --git a/transport.c b/transport.c -index 733717d..2dbdca0 100644 ---- a/transport.c -+++ b/transport.c -@@ -894,18 +894,40 @@ static int external_specification_len(const char *url) - return strchr(url, ':') - url; - } - --void transport_check_allowed(const char *type) -+static const struct string_list *protocol_whitelist(void) - { -- struct string_list allowed = STRING_LIST_INIT_DUP; -- const char *v = getenv("GIT_ALLOW_PROTOCOL"); -+ 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); -+ sort_string_list(&allowed); -+ enabled = 1; -+ } else { -+ enabled = 0; -+ } -+ } - -- if (!v) -- return; -+ 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); -+} - -- string_list_split(&allowed, v, ':', -1); -- if (!unsorted_string_list_has_string(&allowed, type)) -+void transport_check_allowed(const char *type) -+{ -+ if (!is_transport_allowed(type)) - die("transport '%s' not allowed", type); -- string_list_clear(&allowed, 0); -+} -+ -+int transport_restrict_protocols(void) -+{ -+ return !!protocol_whitelist(); - } - - struct transport *transport_get(struct remote *remote, const char *url) -diff --git a/transport.h b/transport.h -index 2beda7d..7707c27 100644 ---- a/transport.h -+++ b/transport.h -@@ -114,12 +114,23 @@ struct transport { - 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. type should generally be the URL scheme, -- * as described in Documentation/git.txt -+ * 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 */ --- -2.1.0 - diff --git a/0004-http-limit-redirection-to-protocol-whitelist.patch b/0004-http-limit-redirection-to-protocol-whitelist.patch deleted file mode 100644 index 293f16c4ead85ae19fe808cbafed62ba6ceaa61f..0000000000000000000000000000000000000000 --- a/0004-http-limit-redirection-to-protocol-whitelist.patch +++ /dev/null @@ -1,77 +0,0 @@ -From 2d22150270739cd29d0ac6bc329e0a2e2910d7d9 Mon Sep 17 00:00:00 2001 -From: Petr Stodulka -Date: Fri, 23 Oct 2015 17:36:57 +0200 -Subject: [PATCH 4/5] http-limit-redirection-to-protocol-whitelist - -Previously, libcurl would follow redirection to any protocol -it was compiled for support with. This is desirable to allow -redirection from HTTP to HTTPS. However, it would even -successfully allow redirection from HTTP to SFTP, a protocol -that git does not otherwise support at all. Furthermore -git's new protocol-whitelisting could be bypassed by -following a redirect within the remote helper, as it was -only enforced at transport selection time. - -This patch limits redirects within libcurl to HTTP, HTTPS, -FTP and FTPS. If there is a protocol-whitelist present, this -list is limited to those also allowed by the whitelist. As -redirection happens from within libcurl, it is impossible -for an HTTP redirect to a protocol implemented within -another remote helper. - -When the curl version git was compiled with is too old to -support restrictions on protocol redirection, we warn the -user if GIT_ALLOW_PROTOCOL restrictions were requested. This -is a little inaccurate, as even without that variable in the -environment, we would still restrict SFTP, etc, and we do -not warn in that case. But anything else means we would -literally warn every time git accesses an http remote. ---- - http.c | 17 +++++++++++++++++ - 1 file changed, 17 insertions(+) - -diff --git a/http.c b/http.c -index 92aba59..235c2d5 100644 ---- a/http.c -+++ b/http.c -@@ -6,6 +6,7 @@ - #include "credential.h" - #include "version.h" - #include "pkt-line.h" -+#include "transport.h" - - int active_requests; - int http_is_verbose; -@@ -252,6 +253,7 @@ static int has_cert_password(void) - static CURL *get_curl_handle(void) - { - CURL *result = curl_easy_init(); -+ long allowed_protocols = 0; - - if (!curl_ssl_verify) { - curl_easy_setopt(result, CURLOPT_SSL_VERIFYPEER, 0); -@@ -301,6 +303,21 @@ static CURL *get_curl_handle(void) - #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); --- -2.1.0 - diff --git a/0005-http-limit-redirection-depth.patch b/0005-http-limit-redirection-depth.patch deleted file mode 100644 index 471f4eb2fbd97f94263a8faca1a5effac4b49468..0000000000000000000000000000000000000000 --- a/0005-http-limit-redirection-depth.patch +++ /dev/null @@ -1,31 +0,0 @@ -From 7f3bfdbc2670b4960242fa1b229dde6bcb2b463b Mon Sep 17 00:00:00 2001 -From: Petr Stodulka -Date: Fri, 23 Oct 2015 17:39:59 +0200 -Subject: [PATCH 5/5] http: limit redirection depth - -By default, libcurl will follow circular http redirects -forever. Let's put a cap on this so that somebody who can -trigger an automated fetch of an arbitrary repository (e.g., -for CI) cannot convince git to loop infinitely. - -The value chosen is 20, which is the same default that -Firefox uses. ---- - http.c | 1 + - 1 file changed, 1 insertion(+) - -diff --git a/http.c b/http.c -index 235c2d5..a1c7dcb 100644 ---- a/http.c -+++ b/http.c -@@ -298,6 +298,7 @@ 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 --- -2.1.0 - diff --git a/0007-git-prompt.patch b/0007-git-prompt.patch deleted file mode 100644 index a17940344b0c445b8a6a3a2579980add59d85b47..0000000000000000000000000000000000000000 --- a/0007-git-prompt.patch +++ /dev/null @@ -1,53 +0,0 @@ -From 7e546ae76da784185ba9515ed86e435ba17fdd65 Mon Sep 17 00:00:00 2001 -From: Petr Stodulka -Date: Wed, 29 Mar 2017 13:08:28 +0200 -Subject: [PATCH] git-prompt.sh: don't put unsanitized branch names in $PS1 - ---- - contrib/completion/git-prompt.sh | 9 ++++++--- - 1 file changed, 6 insertions(+), 3 deletions(-) - -diff --git a/contrib/completion/git-prompt.sh b/contrib/completion/git-prompt.sh -index eaf5c36..2c872e5 100644 ---- a/contrib/completion/git-prompt.sh -+++ b/contrib/completion/git-prompt.sh -@@ -360,8 +360,11 @@ __git_ps1 () - fi - - local f="$w$i$s$u" -+ b=${b##refs/heads/} - if [ $pcmode = yes ]; then - local gitstring= -+ __git_ps1_branch_name=$b -+ b="\${__git_ps1_branch_name}" - if [ -n "${GIT_PS1_SHOWCOLORHINTS-}" ]; then - local c_red='\e[31m' - local c_green='\e[32m' -@@ -371,7 +374,7 @@ __git_ps1 () - local ok_color=$c_green - local branch_color="$c_clear" - local flags_color="$c_lblue" -- local branchstring="$c${b##refs/heads/}" -+ local branchstring="$c$b" - - if [ $detached = no ]; then - branch_color="$ok_color" -@@ -400,13 +403,13 @@ __git_ps1 () - fi - gitstring="$gitstring\[$c_clear\]$r$p" - else -- gitstring="$c${b##refs/heads/}${f:+ $f}$r$p" -+ gitstring="$c$b${f:+ $f}$r$p" - fi - gitstring=$(printf -- "$printf_format" "$gitstring") - PS1="$ps1pc_start$gitstring$ps1pc_end" - else - # NO color option unless in PROMPT_COMMAND mode -- printf -- "$printf_format" "$c${b##refs/heads/}${f:+ $f}$r$p" -+ printf -- "$printf_format" "$c$b${f:+ $f}$r$p" - fi - fi - } --- -2.5.5 - diff --git a/0008-Fix-CVE-2017-8386.patch b/0008-Fix-CVE-2017-8386.patch deleted file mode 100644 index 88b19e9e20e4110320486c418f90a5914816d05f..0000000000000000000000000000000000000000 --- a/0008-Fix-CVE-2017-8386.patch +++ /dev/null @@ -1,26 +0,0 @@ -From 654dbd112ab7cbe0a162afaab645a971da62d433 Mon Sep 17 00:00:00 2001 -From: Petr Stodulka -Date: Wed, 17 May 2017 11:37:01 +0200 -Subject: [PATCH] Fix CVE-2017-8386 - -See the commit 3ec804490 in upstream repository for more info. ---- - shell.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/shell.c b/shell.c -index 1429870..72ed0fa 100644 ---- a/shell.c -+++ b/shell.c -@@ -13,7 +13,7 @@ static int do_generic_cmd(const char *me, char *arg) - const char *my_argv[4]; - - setup_path(); -- if (!arg || !(arg = sq_dequote(arg))) -+ if (!arg || !(arg = sq_dequote(arg)) || *arg == '-') - die("bad argument"); - if (prefixcmp(me, "git-")) - die("bad command"); --- -2.9.4 - diff --git a/0009-remote-curl-fall-back-to-Basic-auth-if-Negotiate-fai.patch b/0009-remote-curl-fall-back-to-Basic-auth-if-Negotiate-fai.patch deleted file mode 100644 index 9c011e2133e51751fe3a028b13f873ff2c8deccd..0000000000000000000000000000000000000000 --- a/0009-remote-curl-fall-back-to-Basic-auth-if-Negotiate-fai.patch +++ /dev/null @@ -1,47 +0,0 @@ -From d6c38a748291246ebe2f7a9e966db24f4b4f839c Mon Sep 17 00:00:00 2001 -From: Petr Stodulka -Date: Wed, 13 Sep 2017 03:09:59 +0200 -Subject: [PATCH] remote-curl: fall back to Basic auth if Negotiate fails - -See the upstream commit 4dbe66464 ---- - http.c | 9 +++++++++ - 1 file changed, 9 insertions(+) - -diff --git a/http.c b/http.c -index e7c77c0..3320590 100644 ---- a/http.c -+++ b/http.c -@@ -60,6 +60,9 @@ static const char *user_agent; - - static struct credential cert_auth = CREDENTIAL_INIT; - static int ssl_cert_password_required; -+#ifdef LIBCURL_CAN_HANDLE_AUTH_ANY -+static unsigned long http_auth_methods = CURLAUTH_ANY; -+#endif - - static struct curl_slist *pragma_header; - static struct curl_slist *no_pragma_header; -@@ -572,6 +575,9 @@ struct active_request_slot *get_active_slot(void) - curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 0); - curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1); - curl_easy_setopt(slot->curl, CURLOPT_FAILONERROR, 1); -+#ifdef LIBCURL_CAN_HANDLE_AUTH_ANY -+ curl_easy_setopt(slot->curl, CURLOPT_HTTPAUTH, http_auth_methods); -+#endif - if (http_auth.password) - init_curl_http_auth(slot->curl); - -@@ -856,6 +862,9 @@ int handle_curl_result(struct slot_results *results) - credential_reject(&http_auth); - return HTTP_NOAUTH; - } else { -+#ifdef LIBCURL_CAN_HANDLE_AUTH_ANY -+ http_auth_methods &= ~CURLAUTH_GSSNEGOTIATE; -+#endif - credential_fill(&http_auth); - return HTTP_REAUTH; - } --- -2.13.5 - diff --git a/backport-CVE-2024-32002-submodules-submodule-paths-m.patch b/backport-CVE-2024-32002-submodules-submodule-paths-m.patch new file mode 100644 index 0000000000000000000000000000000000000000..34772b49154ef92db7ac5c3b313691dbb76aeadd --- /dev/null +++ b/backport-CVE-2024-32002-submodules-submodule-paths-m.patch @@ -0,0 +1,164 @@ +From 6393e6afd414ab9ebeffe069726440d397cae268 Mon Sep 17 00:00:00 2001 +From: Johannes Schindelin +Date: Fri, 22 Mar 2024 11:19:22 +0100 +Subject: [PATCH] backport CVE-2024-32002 submodules: submodule paths must not + contain symlinks + +mainline inclusion +from v2.43.4 +commit 97065761333fd62db1912d81b489db938d8c991d +category: bugfix +bugzilla: https://nvd.nist.gov/vuln/detail/CVE-2024-32002 +CVE: CVE-2024-32002 + +When creating a submodule path, we must be careful not to follow +symbolic links. Otherwise we may follow a symbolic link pointing to +a gitdir (which are valid symbolic links!) e.g. while cloning. + +On case-insensitive filesystems, however, we blindly replace a directory +that has been created as part of the `clone` operation with a symlink +when the path to the latter differs only in case from the former's path. + +Let's simply avoid this situation by expecting not ever having to +overwrite any existing file/directory/symlink upon cloning. That way, we +won't even replace a directory that we just created. + +This addresses CVE-2024-32002. +confliects: + t/t7406-submodule-update.sh +Reported-by: Filip Hejsek +Signed-off-by: Johannes Schindelin +Signed-off-by: qiaojijun +--- + builtin/submodule--helper.c | 35 +++++++++++++++++++++++++++ + t/t7406-submodule-update.sh | 48 +++++++++++++++++++++++++++++++++++++ + 2 files changed, 83 insertions(+) + +diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c +index cce4645..c46d420 100644 +--- a/builtin/submodule--helper.c ++++ b/builtin/submodule--helper.c +@@ -1663,12 +1663,35 @@ static char *clone_submodule_sm_gitdir(const char *name) + return sm_gitdir; + } + ++static int dir_contains_only_dotgit(const char *path) ++{ ++ DIR *dir = opendir(path); ++ struct dirent *e; ++ int ret = 1; ++ ++ if (!dir) ++ return 0; ++ ++ e = readdir_skip_dot_and_dotdot(dir); ++ if (!e) ++ ret = 0; ++ else if (strcmp(DEFAULT_GIT_DIR_ENVIRONMENT, e->d_name) || ++ (e = readdir_skip_dot_and_dotdot(dir))) { ++ error("unexpected item '%s' in '%s'", e->d_name, path); ++ ret = 0; ++ } ++ ++ closedir(dir); ++ return ret; ++} ++ + static int clone_submodule(const struct module_clone_data *clone_data, + struct string_list *reference) + { + char *p; + char *sm_gitdir = clone_submodule_sm_gitdir(clone_data->name); + char *sm_alternate = NULL, *error_strategy = NULL; ++ struct stat st; + struct child_process cp = CHILD_PROCESS_INIT; + const char *clone_data_path = clone_data->path; + char *to_free = NULL; +@@ -1682,6 +1705,10 @@ static int clone_submodule(const struct module_clone_data *clone_data, + "git dir"), sm_gitdir); + + if (!file_exists(sm_gitdir)) { ++ if (clone_data->require_init && !stat(clone_data_path, &st) && ++ !is_empty_dir(clone_data_path)) ++ die(_("directory not empty: '%s'"), clone_data_path); ++ + if (safe_create_leading_directories_const(sm_gitdir) < 0) + die(_("could not create directory '%s'"), sm_gitdir); + +@@ -1726,6 +1753,14 @@ static int clone_submodule(const struct module_clone_data *clone_data, + if(run_command(&cp)) + die(_("clone of '%s' into submodule path '%s' failed"), + clone_data->url, clone_data_path); ++ ++ if (clone_data->require_init && !stat(clone_data_path, &st) && ++ !dir_contains_only_dotgit(clone_data_path)) { ++ char *dot_git = xstrfmt("%s/.git", clone_data_path); ++ unlink(dot_git); ++ free(dot_git); ++ die(_("directory not empty: '%s'"), clone_data_path); ++ } + } else { + char *path; + +diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh +index 8491b8c..1f98b01 100755 +--- a/t/t7406-submodule-update.sh ++++ b/t/t7406-submodule-update.sh +@@ -1179,6 +1179,54 @@ test_expect_success 'submodule update --recursive skip submodules with strategy= + test_cmp expect.err actual.err + ' + ++test_expect_success CASE_INSENSITIVE_FS,SYMLINKS \ ++ 'submodule paths must not follow symlinks' ' ++ ++ # This is only needed because we want to run this in a self-contained ++ # test without having to spin up an HTTP server; However, it would not ++ # be needed in a real-world scenario where the submodule is simply ++ # hosted on a public site. ++ test_config_global protocol.file.allow always && ++ ++ # Make sure that Git tries to use symlinks on Windows ++ test_config_global core.symlinks true && ++ ++ tell_tale_path="$PWD/tell.tale" && ++ git init hook && ++ ( ++ cd hook && ++ mkdir -p y/hooks && ++ write_script y/hooks/post-checkout <<-EOF && ++ echo HOOK-RUN >&2 ++ echo hook-run >"$tell_tale_path" ++ EOF ++ git add y/hooks/post-checkout && ++ test_tick && ++ git commit -m post-checkout ++ ) && ++ ++ hook_repo_path="$(pwd)/hook" && ++ git init captain && ++ ( ++ cd captain && ++ git submodule add --name x/y "$hook_repo_path" A/modules/x && ++ test_tick && ++ git commit -m add-submodule && ++ ++ printf .git >dotgit.txt && ++ git hash-object -w --stdin dot-git.hash && ++ printf "120000 %s 0\ta\n" "$(cat dot-git.hash)" >index.info && ++ git update-index --index-info err && ++ grep "directory not empty" err && ++ test_path_is_missing "$tell_tale_path" ++' ++ + add_submodule_commit_and_validate () { + HASH=$(git rev-parse HEAD) && + git update-index --add --cacheinfo 160000,$HASH,sub && +-- +2.20.1 + diff --git a/backport-CVE-2024-32004-fetch-clone-detect-dubious-ownership-of-local-reposi.patch b/backport-CVE-2024-32004-fetch-clone-detect-dubious-ownership-of-local-reposi.patch new file mode 100644 index 0000000000000000000000000000000000000000..271da261fca1bcac31098a4a035f5da7ad7c220e --- /dev/null +++ b/backport-CVE-2024-32004-fetch-clone-detect-dubious-ownership-of-local-reposi.patch @@ -0,0 +1,153 @@ +From f4aa8c8bb11dae6e769cd930565173808cbb69c8 Mon Sep 17 00:00:00 2001 +From: Johannes Schindelin +Date: Wed, 10 Apr 2024 14:39:37 +0200 +Subject: [PATCH] fetch/clone: detect dubious ownership of local repositories + +When cloning from somebody else's repositories, it is possible that, +say, the `upload-pack` command is overridden in the repository that is +about to be cloned, which would then be run in the user's context who +started the clone. + +To remind the user that this is a potentially unsafe operation, let's +extend the ownership checks we have already established for regular +gitdir discovery to extend also to local repositories that are about to +be cloned. + +This protection extends also to file:// URLs. + +The fixes in this commit address CVE-2024-32004. + +Note: This commit does not touch the `fetch`/`clone` code directly, but +instead the function used implicitly by both: `enter_repo()`. This +function is also used by `git receive-pack` (i.e. pushes), by `git +upload-archive`, by `git daemon` and by `git http-backend`. In setups +that want to serve repositories owned by different users than the +account running the service, this will require `safe.*` settings to be +configured accordingly. + +Also note: there are tiny time windows where a time-of-check-time-of-use +("TOCTOU") race is possible. The real solution to those would be to work +with `fstat()` and `openat()`. However, the latter function is not +available on Windows (and would have to be emulated with rather +expensive low-level `NtCreateFile()` calls), and the changes would be +quite extensive, for my taste too extensive for the little gain given +that embargoed releases need to pay extra attention to avoid introducing +inadvertent bugs. + +Signed-off-by: Johannes Schindelin +--- + setup.h | 12 ++++++++++++ + path.c | 2 ++ + setup.c | 21 +++++++++++++++++++++ + t/t0411-clone-from-partial.sh | 6 +++--- + 4 files changed, 38 insertions(+), 3 deletions(-) + +diff --git a/setup.h b/setup.h +index fcf49706a..a46a3e4b6 100644 +--- a/setup.h ++++ b/setup.h +@@ -41,6 +41,18 @@ const char *read_gitfile_gently(const char *path, int *return_error_code); + const char *resolve_gitdir_gently(const char *suspect, int *return_error_code); + #define resolve_gitdir(path) resolve_gitdir_gently((path), NULL) + ++/* ++ * Check if a repository is safe and die if it is not, by verifying the ++ * ownership of the worktree (if any), the git directory, and the gitfile (if ++ * any). ++ * ++ * Exemptions for known-safe repositories can be added via `safe.directory` ++ * config settings; for non-bare repositories, their worktree needs to be ++ * added, for bare ones their git directory. ++ */ ++void die_upon_dubious_ownership(const char *gitfile, const char *worktree, ++ const char *gitdir); ++ + void setup_work_tree(void); + + /* +diff --git a/path.c b/path.c +index 492e17ad1..d61f70e87 100644 +--- a/path.c ++++ b/path.c +@@ -840,6 +840,7 @@ const char *enter_repo(const char *path, int strict) + if (!suffix[i]) + return NULL; + gitfile = read_gitfile(used_path.buf); ++ die_upon_dubious_ownership(gitfile, NULL, used_path.buf); + if (gitfile) { + strbuf_reset(&used_path); + strbuf_addstr(&used_path, gitfile); +@@ -850,6 +851,7 @@ const char *enter_repo(const char *path, int strict) + } + else { + const char *gitfile = read_gitfile(path); ++ die_upon_dubious_ownership(gitfile, NULL, path); + if (gitfile) + path = gitfile; + if (chdir(path)) +diff --git a/setup.c b/setup.c +index cefd5f63c..9d401ae4c 100644 +--- a/setup.c ++++ b/setup.c +@@ -1165,6 +1165,27 @@ static int ensure_valid_ownership(const char *gitfile, + return data.is_safe; + } + ++void die_upon_dubious_ownership(const char *gitfile, const char *worktree, ++ const char *gitdir) ++{ ++ struct strbuf report = STRBUF_INIT, quoted = STRBUF_INIT; ++ const char *path; ++ ++ if (ensure_valid_ownership(gitfile, worktree, gitdir, &report)) ++ return; ++ ++ strbuf_complete(&report, '\n'); ++ path = gitfile ? gitfile : gitdir; ++ sq_quote_buf_pretty("ed, path); ++ ++ die(_("detected dubious ownership in repository at '%s'\n" ++ "%s" ++ "To add an exception for this directory, call:\n" ++ "\n" ++ "\tgit config --global --add safe.directory %s"), ++ path, report.buf, quoted.buf); ++} ++ + static int allowed_bare_repo_cb(const char *key, const char *value, + const struct config_context *ctx UNUSED, + void *d) +diff --git a/t/t0411-clone-from-partial.sh b/t/t0411-clone-from-partial.sh +index fb72a0a9f..eb3360dbc 100755 +--- a/t/t0411-clone-from-partial.sh ++++ b/t/t0411-clone-from-partial.sh +@@ -23,7 +23,7 @@ test_expect_success 'create evil repo' ' + >evil/.git/shallow + ' + +-test_expect_failure 'local clone must not fetch from promisor remote and execute script' ' ++test_expect_success 'local clone must not fetch from promisor remote and execute script' ' + rm -f script-executed && + test_must_fail git clone \ + --upload-pack="GIT_TEST_ASSUME_DIFFERENT_OWNER=true git-upload-pack" \ +@@ -32,7 +32,7 @@ test_expect_failure 'local clone must not fetch from promisor remote and execute + test_path_is_missing script-executed + ' + +-test_expect_failure 'clone from file://... must not fetch from promisor remote and execute script' ' ++test_expect_success 'clone from file://... must not fetch from promisor remote and execute script' ' + rm -f script-executed && + test_must_fail git clone \ + --upload-pack="GIT_TEST_ASSUME_DIFFERENT_OWNER=true git-upload-pack" \ +@@ -41,7 +41,7 @@ test_expect_failure 'clone from file://... must not fetch from promisor remote a + test_path_is_missing script-executed + ' + +-test_expect_failure 'fetch from file://... must not fetch from promisor remote and execute script' ' ++test_expect_success 'fetch from file://... must not fetch from promisor remote and execute script' ' + rm -f script-executed && + test_must_fail git fetch \ + --upload-pack="GIT_TEST_ASSUME_DIFFERENT_OWNER=true git-upload-pack" \ +-- +2.33.0 + diff --git a/backport-CVE-2024-32004-t0411-add-tests-for-cloning-from-partial-repo.patch b/backport-CVE-2024-32004-t0411-add-tests-for-cloning-from-partial-repo.patch new file mode 100644 index 0000000000000000000000000000000000000000..905f71d484c90d756b45df1025be0a7c7fe472b8 --- /dev/null +++ b/backport-CVE-2024-32004-t0411-add-tests-for-cloning-from-partial-repo.patch @@ -0,0 +1,90 @@ +From 5c5a4a1c05932378d259b1fdd9526cab971656a2 Mon Sep 17 00:00:00 2001 +From: Filip Hejsek +Date: Sun, 28 Jan 2024 04:29:33 +0100 +Subject: [PATCH] t0411: add tests for cloning from partial repo + +Cloning from a partial repository must not fetch missing objects into +the partial repository, because that can lead to arbitrary code +execution. + +Add a couple of test cases, pretending to the `upload-pack` command (and +to that command only) that it is working on a repository owned by +someone else. + +Helped-by: Jeff King +Signed-off-by: Filip Hejsek +Signed-off-by: Johannes Schindelin +--- + t/t0411-clone-from-partial.sh | 60 +++++++++++++++++++++++++++++++++++ + 1 file changed, 60 insertions(+) + create mode 100755 t/t0411-clone-from-partial.sh + +diff --git a/t/t0411-clone-from-partial.sh b/t/t0411-clone-from-partial.sh +new file mode 100755 +index 000000000..fb72a0a9f +--- /dev/null ++++ b/t/t0411-clone-from-partial.sh +@@ -0,0 +1,60 @@ ++#!/bin/sh ++ ++test_description='check that local clone does not fetch from promisor remotes' ++ ++. ./test-lib.sh ++ ++test_expect_success 'create evil repo' ' ++ git init tmp && ++ test_commit -C tmp a && ++ git -C tmp config uploadpack.allowfilter 1 && ++ git clone --filter=blob:none --no-local --no-checkout tmp evil && ++ rm -rf tmp && ++ ++ git -C evil config remote.origin.uploadpack \"\$TRASH_DIRECTORY/fake-upload-pack\" && ++ write_script fake-upload-pack <<-\EOF && ++ echo >&2 "fake-upload-pack running" ++ >"$TRASH_DIRECTORY/script-executed" ++ exit 1 ++ EOF ++ export TRASH_DIRECTORY && ++ ++ # empty shallow file disables local clone optimization ++ >evil/.git/shallow ++' ++ ++test_expect_failure 'local clone must not fetch from promisor remote and execute script' ' ++ rm -f script-executed && ++ test_must_fail git clone \ ++ --upload-pack="GIT_TEST_ASSUME_DIFFERENT_OWNER=true git-upload-pack" \ ++ evil clone1 2>err && ++ ! grep "fake-upload-pack running" err && ++ test_path_is_missing script-executed ++' ++ ++test_expect_failure 'clone from file://... must not fetch from promisor remote and execute script' ' ++ rm -f script-executed && ++ test_must_fail git clone \ ++ --upload-pack="GIT_TEST_ASSUME_DIFFERENT_OWNER=true git-upload-pack" \ ++ "file://$(pwd)/evil" clone2 2>err && ++ ! grep "fake-upload-pack running" err && ++ test_path_is_missing script-executed ++' ++ ++test_expect_failure 'fetch from file://... must not fetch from promisor remote and execute script' ' ++ rm -f script-executed && ++ test_must_fail git fetch \ ++ --upload-pack="GIT_TEST_ASSUME_DIFFERENT_OWNER=true git-upload-pack" \ ++ "file://$(pwd)/evil" 2>err && ++ ! grep "fake-upload-pack running" err && ++ test_path_is_missing script-executed ++' ++ ++test_expect_success 'pack-objects should fetch from promisor remote and execute script' ' ++ rm -f script-executed && ++ echo "HEAD" | test_must_fail git -C evil pack-objects --revs --stdout >/dev/null 2>err && ++ grep "fake-upload-pack running" err && ++ test_path_is_file script-executed ++' ++ ++test_done +-- +2.33.0 + diff --git a/backport-CVE-2024-32020-builtin-clone-refuse-local-clones-of-unsafe-reposito.patch b/backport-CVE-2024-32020-builtin-clone-refuse-local-clones-of-unsafe-reposito.patch new file mode 100644 index 0000000000000000000000000000000000000000..79abdf48b05e50c85a4fefb6b283c644a46da1c7 --- /dev/null +++ b/backport-CVE-2024-32020-builtin-clone-refuse-local-clones-of-unsafe-reposito.patch @@ -0,0 +1,109 @@ +From 1204e1a824c34071019fe106348eaa6d88f9528d Mon Sep 17 00:00:00 2001 +From: Patrick Steinhardt +Date: Mon, 15 Apr 2024 13:30:41 +0200 +Subject: [PATCH] builtin/clone: refuse local clones of unsafe repositories + +When performing a local clone of a repository we end up either copying +or hardlinking the source repository into the target repository. This is +significantly more performant than if we were to use git-upload-pack(1) +and git-fetch-pack(1) to create the new repository and preserves both +disk space and compute time. + +Unfortunately though, performing such a local clone of a repository that +is not owned by the current user is inherently unsafe: + + - It is possible that source files get swapped out underneath us while + we are copying or hardlinking them. While we do perform some checks + here to assert that we hardlinked the expected file, they cannot + reliably thwart time-of-check-time-of-use (TOCTOU) style races. It + is thus possible for an adversary to make us copy or hardlink + unexpected files into the target directory. + + Ideally, we would address this by starting to use openat(3P), + fstatat(3P) and friends. Due to platform compatibility with Windows + we cannot easily do that though. Furthermore, the scope of these + fixes would likely be quite broad and thus not fit for an embargoed + security release. + + - Even if we handled TOCTOU-style races perfectly, hardlinking files + owned by a different user into the target repository is not a good + idea in general. It is possible for an adversary to rewrite those + files to contain whatever data they want even after the clone has + completed. + +Address these issues by completely refusing local clones of a repository +that is not owned by the current user. This reuses our existing infra we +have in place via `ensure_valid_ownership()` and thus allows a user to +override the safety guard by adding the source repository path to the +"safe.directory" configuration. + +This addresses CVE-2024-32020. + +Signed-off-by: Patrick Steinhardt +Signed-off-by: Johannes Schindelin +--- + builtin/clone.c | 14 ++++++++++++++ + t/t0033-safe-directory.sh | 24 ++++++++++++++++++++++++ + 2 files changed, 38 insertions(+) + +diff --git a/builtin/clone.c b/builtin/clone.c +index 4b80fa087..9ec500d42 100644 +--- a/builtin/clone.c ++++ b/builtin/clone.c +@@ -321,6 +321,20 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest, + struct dir_iterator *iter; + int iter_status; + ++ /* ++ * Refuse copying directories by default which aren't owned by us. The ++ * code that performs either the copying or hardlinking is not prepared ++ * to handle various edge cases where an adversary may for example ++ * racily swap out files for symlinks. This can cause us to ++ * inadvertently use the wrong source file. ++ * ++ * Furthermore, even if we were prepared to handle such races safely, ++ * creating hardlinks across user boundaries is an inherently unsafe ++ * operation as the hardlinked files can be rewritten at will by the ++ * potentially-untrusted user. We thus refuse to do so by default. ++ */ ++ die_upon_dubious_ownership(NULL, NULL, src_repo); ++ + mkdir_if_missing(dest->buf, 0777); + + iter = dir_iterator_begin(src->buf, DIR_ITERATOR_PEDANTIC); +diff --git a/t/t0033-safe-directory.sh b/t/t0033-safe-directory.sh +index dc3496897..11c3e8f28 100755 +--- a/t/t0033-safe-directory.sh ++++ b/t/t0033-safe-directory.sh +@@ -80,4 +80,28 @@ test_expect_success 'safe.directory in included file' ' + git status + ' + ++test_expect_success 'local clone of unowned repo refused in unsafe directory' ' ++ test_when_finished "rm -rf source" && ++ git init source && ++ ( ++ sane_unset GIT_TEST_ASSUME_DIFFERENT_OWNER && ++ test_commit -C source initial ++ ) && ++ test_must_fail git clone --local source target && ++ test_path_is_missing target ++' ++ ++test_expect_success 'local clone of unowned repo accepted in safe directory' ' ++ test_when_finished "rm -rf source" && ++ git init source && ++ ( ++ sane_unset GIT_TEST_ASSUME_DIFFERENT_OWNER && ++ test_commit -C source initial ++ ) && ++ test_must_fail git clone --local source target && ++ git config --global --add safe.directory "$(pwd)/source/.git" && ++ git clone --local source target && ++ test_path_is_dir target ++' ++ + test_done +-- +2.33.0 + diff --git a/backport-CVE-2024-32021-builtin-clone-abort-when-hardlinked-source-and-targe.patch b/backport-CVE-2024-32021-builtin-clone-abort-when-hardlinked-source-and-targe.patch new file mode 100644 index 0000000000000000000000000000000000000000..2afafc77e526234934533027185f38c1f8483242 --- /dev/null +++ b/backport-CVE-2024-32021-builtin-clone-abort-when-hardlinked-source-and-targe.patch @@ -0,0 +1,60 @@ +From d1bb66a546b4bb46005d17ba711caaad26f26c1e Mon Sep 17 00:00:00 2001 +From: Patrick Steinhardt +Date: Mon, 15 Apr 2024 13:30:31 +0200 +Subject: [PATCH] builtin/clone: abort when hardlinked source and target file + differ + +When performing local clones with hardlinks we refuse to copy source +files which are symlinks as a mitigation for CVE-2022-39253. This check +can be raced by an adversary though by changing the file to a symlink +after we have checked it. + +Fix the issue by checking whether the hardlinked destination file +matches the source file and abort in case it doesn't. + +This addresses CVE-2024-32021. + +Reported-by: Apple Product Security +Suggested-by: Linus Torvalds +Signed-off-by: Patrick Steinhardt +Signed-off-by: Johannes Schindelin +--- + builtin/clone.c | 21 ++++++++++++++++++++- + 1 file changed, 20 insertions(+), 1 deletion(-) + +diff --git a/builtin/clone.c b/builtin/clone.c +index 073e6323d..4b80fa087 100644 +--- a/builtin/clone.c ++++ b/builtin/clone.c +@@ -357,8 +357,27 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest, + if (unlink(dest->buf) && errno != ENOENT) + die_errno(_("failed to unlink '%s'"), dest->buf); + if (!option_no_hardlinks) { +- if (!link(src->buf, dest->buf)) ++ if (!link(src->buf, dest->buf)) { ++ struct stat st; ++ ++ /* ++ * Sanity-check whether the created hardlink ++ * actually links to the expected file now. This ++ * catches time-of-check-time-of-use bugs in ++ * case the source file was meanwhile swapped. ++ */ ++ if (lstat(dest->buf, &st)) ++ die(_("hardlink cannot be checked at '%s'"), dest->buf); ++ if (st.st_mode != iter->st.st_mode || ++ st.st_ino != iter->st.st_ino || ++ st.st_dev != iter->st.st_dev || ++ st.st_size != iter->st.st_size || ++ st.st_uid != iter->st.st_uid || ++ st.st_gid != iter->st.st_gid) ++ die(_("hardlink different from source at '%s'"), dest->buf); ++ + continue; ++ } + if (option_local > 0) + die_errno(_("failed to create link '%s'"), dest->buf); + option_no_hardlinks = 1; +-- +2.33.0 + diff --git a/backport-CVE-2024-32021-builtin-clone-stop-resolving-symlinks-when-copying-f.patch b/backport-CVE-2024-32021-builtin-clone-stop-resolving-symlinks-when-copying-f.patch new file mode 100644 index 0000000000000000000000000000000000000000..fb0fc5e6c3b52a13a0ab63cfdacea911085a9486 --- /dev/null +++ b/backport-CVE-2024-32021-builtin-clone-stop-resolving-symlinks-when-copying-f.patch @@ -0,0 +1,84 @@ +From 150e6b0aedf57d224c3c49038c306477fa159886 Mon Sep 17 00:00:00 2001 +From: Patrick Steinhardt +Date: Mon, 15 Apr 2024 13:30:26 +0200 +Subject: [PATCH] builtin/clone: stop resolving symlinks when copying files + +When a user performs a local clone without `--no-local`, then we end up +copying the source repository into the target repository directly. To +optimize this even further, we try to hardlink files into place instead +of copying data over, which helps both disk usage and speed. + +There is an important edge case in this context though, namely when we +try to hardlink symlinks from the source repository into the target +repository. Depending on both platform and filesystem the resulting +behaviour here can be different: + + - On macOS and NetBSD, calling link(3P) with a symlink target creates + a hardlink to the file pointed to by the symlink. + + - On Linux, calling link(3P) instead creates a hardlink to the symlink + itself. + +To unify this behaviour, 36596fd2df (clone: better handle symlinked +files at .git/objects/, 2019-07-10) introduced logic to resolve symlinks +before we try to link(3P) files. Consequently, the new behaviour was to +always create a hard link to the target of the symlink on all platforms. + +Eventually though, we figured out that following symlinks like this can +cause havoc when performing a local clone of a malicious repository, +which resulted in CVE-2022-39253. This issue was fixed via 6f054f9fb3 +(builtin/clone.c: disallow `--local` clones with symlinks, 2022-07-28), +by refusing symlinks in the source repository. + +But even though we now shouldn't ever link symlinks anymore, the code +that resolves symlinks still exists. In the best case the code does not +end up doing anything because there are no symlinks anymore. In the +worst case though this can be abused by an adversary that rewrites the +source file after it has been checked not to be a symlink such that it +actually is a symlink when we call link(3P). Thus, it is still possible +to recreate CVE-2022-39253 due to this time-of-check-time-of-use bug. + +Remove the call to `realpath()`. This doesn't yet address the actual +vulnerability, which will be handled in a subsequent commit. + +Reported-by: Apple Product Security +Signed-off-by: Patrick Steinhardt +Signed-off-by: Johannes Schindelin +--- + builtin/clone.c | 6 +----- + 1 file changed, 1 insertion(+), 5 deletions(-) + +diff --git a/builtin/clone.c b/builtin/clone.c +index 3c2ae31a5..073e6323d 100644 +--- a/builtin/clone.c ++++ b/builtin/clone.c +@@ -320,7 +320,6 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest, + int src_len, dest_len; + struct dir_iterator *iter; + int iter_status; +- struct strbuf realpath = STRBUF_INIT; + + mkdir_if_missing(dest->buf, 0777); + +@@ -358,8 +357,7 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest, + if (unlink(dest->buf) && errno != ENOENT) + die_errno(_("failed to unlink '%s'"), dest->buf); + if (!option_no_hardlinks) { +- strbuf_realpath(&realpath, src->buf, 1); +- if (!link(realpath.buf, dest->buf)) ++ if (!link(src->buf, dest->buf)) + continue; + if (option_local > 0) + die_errno(_("failed to create link '%s'"), dest->buf); +@@ -373,8 +371,6 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest, + strbuf_setlen(src, src_len); + die(_("failed to iterate over '%s'"), src->buf); + } +- +- strbuf_release(&realpath); + } + + static void clone_local(const char *src_repo, const char *dest_repo) +-- +2.33.0 + diff --git a/backport-CVE-2024-32465-upload-pack-disable-lazy-fetching-by-default.patch b/backport-CVE-2024-32465-upload-pack-disable-lazy-fetching-by-default.patch new file mode 100644 index 0000000000000000000000000000000000000000..a5b8b6a9aa77ce5be12c3d9fd94c70ff7997762d --- /dev/null +++ b/backport-CVE-2024-32465-upload-pack-disable-lazy-fetching-by-default.patch @@ -0,0 +1,201 @@ +From 7b70e9efb18c2cc3f219af399bd384c5801ba1d7 Mon Sep 17 00:00:00 2001 +From: Jeff King +Date: Tue, 16 Apr 2024 04:35:33 -0400 +Subject: [PATCH] upload-pack: disable lazy-fetching by default + +The upload-pack command tries to avoid trusting the repository in which +it's run (e.g., by not running any hooks and not using any config that +contains arbitrary commands). But if the server side of a fetch or a +clone is a partial clone, then either upload-pack or its child +pack-objects may run a lazy "git fetch" under the hood. And it is very +easy to convince fetch to run arbitrary commands. + +The "server" side can be a local repository owned by someone else, who +would be able to configure commands that are run during a clone with the +current user's permissions. This issue has been designated +CVE-2024-32004. + +The fix in this commit's parent helps in this scenario, as well as in +related scenarios using SSH to clone, where the untrusted .git directory +is owned by a different user id. But if you received one as a zip file, +on a USB stick, etc, it may be owned by your user but still untrusted. + +This has been designated CVE-2024-32465. + +To mitigate the issue more completely, let's disable lazy fetching +entirely during `upload-pack`. While fetching from a partial repository +should be relatively rare, it is certainly not an unreasonable workflow. +And thus we need to provide an escape hatch. + +This commit works by respecting a GIT_NO_LAZY_FETCH environment variable +(to skip the lazy-fetch), and setting it in upload-pack, but only when +the user has not already done so (which gives us the escape hatch). + +The name of the variable is specifically chosen to match what has +already been added in 'master' via e6d5479e7a (git: extend +--no-lazy-fetch to work across subprocesses, 2024-02-27). Since we're +building this fix as a backport for older versions, we could cherry-pick +that patch and its earlier steps. However, we don't really need the +niceties (like a "--no-lazy-fetch" option) that it offers. By using the +same name, everything should just work when the two are eventually +merged, but here are a few notes: + + - the blocking of the fetch in e6d5479e7a is incomplete! It sets + fetch_if_missing to 0 when we setup the repository variable, but + that isn't enough. pack-objects in particular will call + prefetch_to_pack() even if that variable is 0. This patch by + contrast checks the environment variable at the lowest level before + we call the lazy fetch, where we can be sure to catch all code + paths. + + Possibly the setting of fetch_if_missing from e6d5479e7a can be + reverted, but it may be useful to have. For example, some code may + want to use that flag to change behavior before it gets to the point + of trying to start the fetch. At any rate, that's all outside the + scope of this patch. + + - there's documentation for GIT_NO_LAZY_FETCH in e6d5479e7a. We can + live without that here, because for the most part the user shouldn't + need to set it themselves. The exception is if they do want to + override upload-pack's default, and that requires a separate + documentation section (which is added here) + + - it would be nice to use the NO_LAZY_FETCH_ENVIRONMENT macro added by + e6d5479e7a, but those definitions have moved from cache.h to + environment.h between 2.39.3 and master. I just used the raw string + literals, and we can replace them with the macro once this topic is + merged to master. + +At least with respect to CVE-2024-32004, this does render this commit's +parent commit somewhat redundant. However, it is worth retaining that +commit as defense in depth, and because it may help other issues (e.g., +symlink/hardlink TOCTOU races, where zip files are not really an +interesting attack vector). + +The tests in t0411 still pass, but now we have _two_ mechanisms ensuring +that the evil command is not run. Let's beef up the existing ones to +check that they failed for the expected reason, that we refused to run +upload-pack at all with an alternate user id. And add two new ones for +the same-user case that both the restriction and its escape hatch. + +Signed-off-by: Jeff King +Signed-off-by: Johannes Schindelin +--- + Documentation/git-upload-pack.txt | 16 ++++++++++++++++ + builtin/upload-pack.c | 2 ++ + promisor-remote.c | 10 ++++++++++ + t/t0411-clone-from-partial.sh | 18 ++++++++++++++++++ + 4 files changed, 46 insertions(+) + +diff --git a/Documentation/git-upload-pack.txt b/Documentation/git-upload-pack.txt +index b656b4756..fc4c62d7b 100644 +--- a/Documentation/git-upload-pack.txt ++++ b/Documentation/git-upload-pack.txt +@@ -55,6 +55,22 @@ ENVIRONMENT + admins may need to configure some transports to allow this + variable to be passed. See the discussion in linkgit:git[1]. + ++`GIT_NO_LAZY_FETCH`:: ++ When cloning or fetching from a partial repository (i.e., one ++ itself cloned with `--filter`), the server-side `upload-pack` ++ may need to fetch extra objects from its upstream in order to ++ complete the request. By default, `upload-pack` will refuse to ++ perform such a lazy fetch, because `git fetch` may run arbitrary ++ commands specified in configuration and hooks of the source ++ repository (and `upload-pack` tries to be safe to run even in ++ untrusted `.git` directories). +++ ++This is implemented by having `upload-pack` internally set the ++`GIT_NO_LAZY_FETCH` variable to `1`. If you want to override it ++(because you are fetching from a partial clone, and you are sure ++you trust it), you can explicitly set `GIT_NO_LAZY_FETCH` to ++`0`. ++ + SEE ALSO + -------- + linkgit:gitnamespaces[7] +diff --git a/builtin/upload-pack.c b/builtin/upload-pack.c +index 25b69da2b..f446ff04f 100644 +--- a/builtin/upload-pack.c ++++ b/builtin/upload-pack.c +@@ -35,6 +35,8 @@ int cmd_upload_pack(int argc, const char **argv, const char *prefix) + + packet_trace_identity("upload-pack"); + disable_replace_refs(); ++ /* TODO: This should use NO_LAZY_FETCH_ENVIRONMENT */ ++ xsetenv("GIT_NO_LAZY_FETCH", "1", 0); + + argc = parse_options(argc, argv, prefix, options, upload_pack_usage, 0); + +diff --git a/promisor-remote.c b/promisor-remote.c +index faa761294..550a38f75 100644 +--- a/promisor-remote.c ++++ b/promisor-remote.c +@@ -20,6 +20,16 @@ static int fetch_objects(struct repository *repo, + int i; + FILE *child_in; + ++ /* TODO: This should use NO_LAZY_FETCH_ENVIRONMENT */ ++ if (git_env_bool("GIT_NO_LAZY_FETCH", 0)) { ++ static int warning_shown; ++ if (!warning_shown) { ++ warning_shown = 1; ++ warning(_("lazy fetching disabled; some objects may not be available")); ++ } ++ return -1; ++ } ++ + child.git_cmd = 1; + child.in = -1; + if (repo != the_repository) +diff --git a/t/t0411-clone-from-partial.sh b/t/t0411-clone-from-partial.sh +index eb3360dbc..b3d6ddc4b 100755 +--- a/t/t0411-clone-from-partial.sh ++++ b/t/t0411-clone-from-partial.sh +@@ -28,6 +28,7 @@ test_expect_success 'local clone must not fetch from promisor remote and execute + test_must_fail git clone \ + --upload-pack="GIT_TEST_ASSUME_DIFFERENT_OWNER=true git-upload-pack" \ + evil clone1 2>err && ++ grep "detected dubious ownership" err && + ! grep "fake-upload-pack running" err && + test_path_is_missing script-executed + ' +@@ -37,6 +38,7 @@ test_expect_success 'clone from file://... must not fetch from promisor remote a + test_must_fail git clone \ + --upload-pack="GIT_TEST_ASSUME_DIFFERENT_OWNER=true git-upload-pack" \ + "file://$(pwd)/evil" clone2 2>err && ++ grep "detected dubious ownership" err && + ! grep "fake-upload-pack running" err && + test_path_is_missing script-executed + ' +@@ -46,6 +48,7 @@ test_expect_success 'fetch from file://... must not fetch from promisor remote a + test_must_fail git fetch \ + --upload-pack="GIT_TEST_ASSUME_DIFFERENT_OWNER=true git-upload-pack" \ + "file://$(pwd)/evil" 2>err && ++ grep "detected dubious ownership" err && + ! grep "fake-upload-pack running" err && + test_path_is_missing script-executed + ' +@@ -57,4 +60,19 @@ test_expect_success 'pack-objects should fetch from promisor remote and execute + test_path_is_file script-executed + ' + ++test_expect_success 'clone from promisor remote does not lazy-fetch by default' ' ++ rm -f script-executed && ++ test_must_fail git clone evil no-lazy 2>err && ++ grep "lazy fetching disabled" err && ++ test_path_is_missing script-executed ++' ++ ++test_expect_success 'promisor lazy-fetching can be re-enabled' ' ++ rm -f script-executed && ++ test_must_fail env GIT_NO_LAZY_FETCH=0 \ ++ git clone evil lazy-ok 2>err && ++ grep "fake-upload-pack running" err && ++ test_path_is_file script-executed ++' ++ + test_done +-- +2.33.0 + diff --git a/backport-CVE-2024-50349-credential-sanitize-the-user-prompt.patch b/backport-CVE-2024-50349-credential-sanitize-the-user-prompt.patch new file mode 100644 index 0000000000000000000000000000000000000000..b0bbeff83f667544dfdc8de2e6c1c43ef700e564 --- /dev/null +++ b/backport-CVE-2024-50349-credential-sanitize-the-user-prompt.patch @@ -0,0 +1,316 @@ +From 7725b8100ffbbff2750ee4d61a0fcc1f53a086e8 Mon Sep 17 00:00:00 2001 +From: Johannes Schindelin +Date: Wed, 30 Oct 2024 13:26:10 +0100 +Subject: [PATCH] credential: sanitize the user prompt + +When asking the user interactively for credentials, we want to avoid +misleading them e.g. via control sequences that pretend that the URL +targets a trusted host when it does not. + +While Git learned, over the course of the preceding commits, to disallow +URLs containing URL-encoded control characters by default, credential +helpers are still allowed to specify values very freely (apart from Line +Feed and NUL characters, anything is allowed), and this would allow, +say, a username containing control characters to be specified that would +then be displayed in the interactive terminal prompt asking the user for +the password, potentially sending those control characters directly to +the terminal. This is undesirable because control characters can be used +to mislead users to divulge secret information to untrusted sites. + +To prevent such an attack vector, let's add a `git_prompt()` that forces +the displayed text to be sanitized, i.e. displaying question marks +instead of control characters. + +Note: While this commit's diff changes a lot of `user@host` strings to +`user%40host`, which may look suspicious on the surface, there is a good +reason for that: this string specifies a user name, not a +@ combination! In the context of t5541, the actual +combination looks like this: `user%40@127.0.0.1:5541`. Therefore, these +string replacements document a net improvement introduced by this +commit, as `user@host@127.0.0.1` could have left readers wondering where +the user name ends and where the host name begins. + +Hinted-at-by: Jeff King +Signed-off-by: Johannes Schindelin +--- + Documentation/config/credential.txt | 6 ++++++ + credential.c | 7 ++++++- + credential.h | 4 +++- + t/t0300-credentials.sh | 20 ++++++++++++++++++++ + t/t5541-http-push-smart.sh | 6 +++--- + t/t5550-http-fetch-dumb.sh | 14 +++++++------- + t/t5551-http-fetch-smart.sh | 16 ++++++++-------- + 7 files changed, 53 insertions(+), 20 deletions(-) + +diff --git a/Documentation/config/credential.txt b/Documentation/config/credential.txt +index 512f3187..fd8113d6 100644 +--- a/Documentation/config/credential.txt ++++ b/Documentation/config/credential.txt +@@ -14,6 +14,12 @@ credential.useHttpPath:: + or https URL to be important. Defaults to false. See + linkgit:gitcredentials[7] for more information. + ++credential.sanitizePrompt:: ++ By default, user names and hosts that are shown as part of the ++ password prompt are not allowed to contain control characters (they ++ will be URL-encoded by default). Configure this setting to `false` to ++ override that behavior. ++ + credential.username:: + If no username is set for a network authentication, use this username + by default. See credential..* below, and +diff --git a/credential.c b/credential.c +index 572f1785..1392a54d 100644 +--- a/credential.c ++++ b/credential.c +@@ -67,6 +67,8 @@ static int credential_config_callback(const char *var, const char *value, + } + else if (!strcmp(key, "usehttppath")) + c->use_http_path = git_config_bool(var, value); ++ else if (!strcmp(key, "sanitizeprompt")) ++ c->sanitize_prompt = git_config_bool(var, value); + + return 0; + } +@@ -179,7 +181,10 @@ static char *credential_ask_one(const char *what, struct credential *c, + struct strbuf prompt = STRBUF_INIT; + char *r; + +- credential_describe(c, &desc); ++ if (c->sanitize_prompt) ++ credential_format(c, &desc); ++ else ++ credential_describe(c, &desc); + if (desc.len) + strbuf_addf(&prompt, "%s for '%s': ", what, desc.buf); + else +diff --git a/credential.h b/credential.h +index 935b28a7..0364d436 100644 +--- a/credential.h ++++ b/credential.h +@@ -119,7 +119,8 @@ struct credential { + configured:1, + quit:1, + use_http_path:1, +- username_from_proto:1; ++ username_from_proto:1, ++ sanitize_prompt:1; + + char *username; + char *password; +@@ -132,6 +133,7 @@ struct credential { + .helpers = STRING_LIST_INIT_DUP, \ + .password_expiry_utc = TIME_MAX, \ + .wwwauth_headers = STRVEC_INIT, \ ++ .sanitize_prompt = 1, \ + } + + /* Initialize a credential structure, setting all fields to empty. */ +diff --git a/t/t0300-credentials.sh b/t/t0300-credentials.sh +index cb91be14..b62c70c1 100755 +--- a/t/t0300-credentials.sh ++++ b/t/t0300-credentials.sh +@@ -45,6 +45,10 @@ test_expect_success 'setup helper scripts' ' + test -z "$pexpiry" || echo password_expiry_utc=$pexpiry + EOF + ++ write_script git-credential-cntrl-in-username <<-\EOF && ++ printf "username=\\007latrix Lestrange\\n" ++ EOF ++ + PATH="$PWD:$PATH" + ' + +@@ -825,4 +829,20 @@ test_expect_success 'credential config with partial URLs' ' + test_grep "skipping credential lookup for key" stderr + ' + ++BEL="$(printf '\007')" ++ ++test_expect_success 'interactive prompt is sanitized' ' ++ check fill cntrl-in-username <<-EOF ++ protocol=https ++ host=example.org ++ -- ++ protocol=https ++ host=example.org ++ username=${BEL}latrix Lestrange ++ password=askpass-password ++ -- ++ askpass: Password for ${SQ}https://%07latrix%20Lestrange@example.org${SQ}: ++ EOF ++' ++ + test_done +diff --git a/t/t5541-http-push-smart.sh b/t/t5541-http-push-smart.sh +index d0211cd8..2cd2e1a0 100755 +--- a/t/t5541-http-push-smart.sh ++++ b/t/t5541-http-push-smart.sh +@@ -351,7 +351,7 @@ test_expect_success 'push over smart http with auth' ' + git push "$HTTPD_URL"/auth/smart/test_repo.git && + git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/test_repo.git" \ + log -1 --format=%s >actual && +- expect_askpass both user@host && ++ expect_askpass both user%40host && + test_cmp expect actual + ' + +@@ -363,7 +363,7 @@ test_expect_success 'push to auth-only-for-push repo' ' + git push "$HTTPD_URL"/auth-push/smart/test_repo.git && + git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/test_repo.git" \ + log -1 --format=%s >actual && +- expect_askpass both user@host && ++ expect_askpass both user%40host && + test_cmp expect actual + ' + +@@ -393,7 +393,7 @@ test_expect_success 'push into half-auth-complete requires password' ' + git push "$HTTPD_URL/half-auth-complete/smart/half-auth.git" && + git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/half-auth.git" \ + log -1 --format=%s >actual && +- expect_askpass both user@host && ++ expect_askpass both user%40host && + test_cmp expect actual + ' + +diff --git a/t/t5550-http-fetch-dumb.sh b/t/t5550-http-fetch-dumb.sh +index 8f182a3c..5d0e3946 100755 +--- a/t/t5550-http-fetch-dumb.sh ++++ b/t/t5550-http-fetch-dumb.sh +@@ -90,13 +90,13 @@ test_expect_success 'http auth can use user/pass in URL' ' + test_expect_success 'http auth can use just user in URL' ' + set_askpass wrong pass@host && + git clone "$HTTPD_URL_USER/auth/dumb/repo.git" clone-auth-pass && +- expect_askpass pass user@host ++ expect_askpass pass user%40host + ' + + test_expect_success 'http auth can request both user and pass' ' + set_askpass user@host pass@host && + git clone "$HTTPD_URL/auth/dumb/repo.git" clone-auth-both && +- expect_askpass both user@host ++ expect_askpass both user%40host + ' + + test_expect_success 'http auth respects credential helper config' ' +@@ -114,14 +114,14 @@ test_expect_success 'http auth can get username from config' ' + test_config_global "credential.$HTTPD_URL.username" user@host && + set_askpass wrong pass@host && + git clone "$HTTPD_URL/auth/dumb/repo.git" clone-auth-user && +- expect_askpass pass user@host ++ expect_askpass pass user%40host + ' + + test_expect_success 'configured username does not override URL' ' + test_config_global "credential.$HTTPD_URL.username" wrong && + set_askpass wrong pass@host && + git clone "$HTTPD_URL_USER/auth/dumb/repo.git" clone-auth-user2 && +- expect_askpass pass user@host ++ expect_askpass pass user%40host + ' + + test_expect_success 'set up repo with http submodules' ' +@@ -142,7 +142,7 @@ test_expect_success 'cmdline credential config passes to submodule via clone' ' + set_askpass wrong pass@host && + git -c "credential.$HTTPD_URL.username=user@host" \ + clone --recursive super super-clone && +- expect_askpass pass user@host ++ expect_askpass pass user%40host + ' + + test_expect_success 'cmdline credential config passes submodule via fetch' ' +@@ -153,7 +153,7 @@ test_expect_success 'cmdline credential config passes submodule via fetch' ' + git -C super-clone \ + -c "credential.$HTTPD_URL.username=user@host" \ + fetch --recurse-submodules && +- expect_askpass pass user@host ++ expect_askpass pass user%40host + ' + + test_expect_success 'cmdline credential config passes submodule update' ' +@@ -170,7 +170,7 @@ test_expect_success 'cmdline credential config passes submodule update' ' + git -C super-clone \ + -c "credential.$HTTPD_URL.username=user@host" \ + submodule update && +- expect_askpass pass user@host ++ expect_askpass pass user%40host + ' + + test_expect_success 'fetch changes via http' ' +diff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh +index 0908534f..8a27768d 100755 +--- a/t/t5551-http-fetch-smart.sh ++++ b/t/t5551-http-fetch-smart.sh +@@ -181,7 +181,7 @@ test_expect_success 'clone from password-protected repository' ' + echo two >expect && + set_askpass user@host pass@host && + git clone --bare "$HTTPD_URL/auth/smart/repo.git" smart-auth && +- expect_askpass both user@host && ++ expect_askpass both user%40host && + git --git-dir=smart-auth log -1 --format=%s >actual && + test_cmp expect actual + ' +@@ -199,7 +199,7 @@ test_expect_success 'clone from auth-only-for-objects repository' ' + echo two >expect && + set_askpass user@host pass@host && + git clone --bare "$HTTPD_URL/auth-fetch/smart/repo.git" half-auth && +- expect_askpass both user@host && ++ expect_askpass both user%40host && + git --git-dir=half-auth log -1 --format=%s >actual && + test_cmp expect actual + ' +@@ -224,14 +224,14 @@ test_expect_success 'redirects send auth to new location' ' + set_askpass user@host pass@host && + git -c credential.useHttpPath=true \ + clone $HTTPD_URL/smart-redir-auth/repo.git repo-redir-auth && +- expect_askpass both user@host auth/smart/repo.git ++ expect_askpass both user%40host auth/smart/repo.git + ' + + test_expect_success 'GIT_TRACE_CURL redacts auth details' ' + rm -rf redact-auth trace && + set_askpass user@host pass@host && + GIT_TRACE_CURL="$(pwd)/trace" git clone --bare "$HTTPD_URL/auth/smart/repo.git" redact-auth && +- expect_askpass both user@host && ++ expect_askpass both user%40host && + + # Ensure that there is no "Basic" followed by a base64 string, but that + # the auth details are redacted +@@ -243,7 +243,7 @@ test_expect_success 'GIT_CURL_VERBOSE redacts auth details' ' + rm -rf redact-auth trace && + set_askpass user@host pass@host && + GIT_CURL_VERBOSE=1 git clone --bare "$HTTPD_URL/auth/smart/repo.git" redact-auth 2>trace && +- expect_askpass both user@host && ++ expect_askpass both user%40host && + + # Ensure that there is no "Basic" followed by a base64 string, but that + # the auth details are redacted +@@ -256,7 +256,7 @@ test_expect_success 'GIT_TRACE_CURL does not redact auth details if GIT_TRACE_RE + set_askpass user@host pass@host && + GIT_TRACE_REDACT=0 GIT_TRACE_CURL="$(pwd)/trace" \ + git clone --bare "$HTTPD_URL/auth/smart/repo.git" redact-auth && +- expect_askpass both user@host && ++ expect_askpass both user%40host && + + grep -i "Authorization: Basic [0-9a-zA-Z+/]" trace + ' +@@ -568,7 +568,7 @@ test_expect_success 'http auth remembers successful credentials' ' + # the first request prompts the user... + set_askpass user@host pass@host && + git ls-remote "$HTTPD_URL/auth/smart/repo.git" >/dev/null && +- expect_askpass both user@host && ++ expect_askpass both user%40host && + + # ...and the second one uses the stored value rather than + # prompting the user. +@@ -599,7 +599,7 @@ test_expect_success 'http auth forgets bogus credentials' ' + # us to prompt the user again. + set_askpass user@host pass@host && + git ls-remote "$HTTPD_URL/auth/smart/repo.git" >/dev/null && +- expect_askpass both user@host ++ expect_askpass both user%40host + ' + + test_expect_success 'client falls back from v2 to v0 to match server' ' +-- +2.23.0 diff --git a/backport-CVE-2024-50349-credential_format-also-encode-host-port.patch b/backport-CVE-2024-50349-credential_format-also-encode-host-port.patch new file mode 100644 index 0000000000000000000000000000000000000000..44cf6579c929c371cd546e71843658fa943d01e5 --- /dev/null +++ b/backport-CVE-2024-50349-credential_format-also-encode-host-port.patch @@ -0,0 +1,98 @@ +From c903985bf7e772e2d08275c1a95c8a55ab011577 Mon Sep 17 00:00:00 2001 +From: Johannes Schindelin +Date: Thu, 7 Nov 2024 08:57:52 +0100 +Subject: [PATCH] credential_format(): also encode [:] + +An upcoming change wants to sanitize the credential password prompt +where a URL is displayed that may potentially come from a `.gitmodules` +file. To this end, the `credential_format()` function is employed. + +To sanitize the host name (and optional port) part of the URL, we need a +new mode of the `strbuf_add_percentencode()` function because the +current mode is both too strict and too lenient: too strict because it +encodes `:`, `[` and `]` (which should be left unencoded in +`:` and in IPv6 addresses), and too lenient because it does +not encode invalid host name characters `/`, `_` and `~`. + +So let's introduce and use a new mode specifically to encode the host +name and optional port part of a URI, leaving alpha-numerical +characters, periods, colons and brackets alone and encoding all others. + +This only leads to a change of behavior for URLs that contain invalid +host names. + +Signed-off-by: Johannes Schindelin +--- + credential.c | 3 ++- + strbuf.c | 4 +++- + strbuf.h | 1 + + t/t0300-credentials.sh | 13 +++++++++++++ + 4 files changed, 19 insertions(+), 2 deletions(-) + +diff --git a/credential.c b/credential.c +index f3201134..572f1785 100644 +--- a/credential.c ++++ b/credential.c +@@ -164,7 +164,8 @@ static void credential_format(struct credential *c, struct strbuf *out) + strbuf_addch(out, '@'); + } + if (c->host) +- strbuf_addstr(out, c->host); ++ strbuf_add_percentencode(out, c->host, ++ STRBUF_ENCODE_HOST_AND_PORT); + if (c->path) { + strbuf_addch(out, '/'); + strbuf_add_percentencode(out, c->path, 0); +diff --git a/strbuf.c b/strbuf.c +index c383f41a..756b96c5 100644 +--- a/strbuf.c ++++ b/strbuf.c +@@ -492,7 +492,9 @@ void strbuf_add_percentencode(struct strbuf *dst, const char *src, int flags) + unsigned char ch = src[i]; + if (ch <= 0x1F || ch >= 0x7F || + (ch == '/' && (flags & STRBUF_ENCODE_SLASH)) || +- strchr(URL_UNSAFE_CHARS, ch)) ++ ((flags & STRBUF_ENCODE_HOST_AND_PORT) ? ++ !isalnum(ch) && !strchr("-.:[]", ch) : ++ !!strchr(URL_UNSAFE_CHARS, ch))) + strbuf_addf(dst, "%%%02X", (unsigned char)ch); + else + strbuf_addch(dst, ch); +diff --git a/strbuf.h b/strbuf.h +index f6dbb968..f9f8bb03 100644 +--- a/strbuf.h ++++ b/strbuf.h +@@ -380,6 +380,7 @@ size_t strbuf_expand_dict_cb(struct strbuf *sb, + void strbuf_addbuf_percentquote(struct strbuf *dst, const struct strbuf *src); + + #define STRBUF_ENCODE_SLASH 1 ++#define STRBUF_ENCODE_HOST_AND_PORT 2 + + /** + * Append the contents of a string to a strbuf, percent-encoding any characters +diff --git a/t/t0300-credentials.sh b/t/t0300-credentials.sh +index c66d91e8..cb91be14 100755 +--- a/t/t0300-credentials.sh ++++ b/t/t0300-credentials.sh +@@ -514,6 +514,19 @@ test_expect_success 'match percent-encoded values in username' ' + EOF + ' + ++test_expect_success 'match percent-encoded values in hostname' ' ++ test_config "credential.https://a%20b%20c/.helper" "$HELPER" && ++ check fill <<-\EOF ++ url=https://a b c/ ++ -- ++ protocol=https ++ host=a b c ++ username=foo ++ password=bar ++ -- ++ EOF ++' ++ + test_expect_success 'fetch with multiple path components' ' + test_unconfig credential.helper && + test_config credential.https://example.com/foo/repo.git.helper "verbatim foo bar" && +-- +2.23.0 diff --git a/backport-CVE-2024-52005.patch b/backport-CVE-2024-52005.patch new file mode 100644 index 0000000000000000000000000000000000000000..660abe28a8bcfca56c83fc52c322165bdfde5977 --- /dev/null +++ b/backport-CVE-2024-52005.patch @@ -0,0 +1,388 @@ +From 5b257412e25ad29410c389300324886aa59e1f83 Mon Sep 17 00:00:00 2001 +From: Johannes Schindelin +Date: Wed, 6 Nov 2024 20:34:50 +0100 +Subject: [PATCH 1/3] sideband: mask control characters + +The output of `git clone` is a vital component for understanding what +has happened when things go wrong. However, these logs are partially +under the control of the remote server (via the "sideband", which +typically contains what the remote `git pack-objects` process sends to +`stderr`), and is currently not sanitized by Git. + +This makes Git susceptible to ANSI escape sequence injection (see +CWE-150, https://cwe.mitre.org/data/definitions/150.html), which allows +attackers to corrupt terminal state, to hide information, and even to +insert characters into the input buffer (i.e. as if the user had typed +those characters). + +To plug this vulnerability, disallow any control character in the +sideband, replacing them instead with the common `^` +(e.g. `^[` for `\x1b`, `^A` for `\x01`). + +There is likely a need for more fine-grained controls instead of using a +"heavy hammer" like this, which will be introduced subsequently. + +Signed-off-by: Johannes Schindelin +--- + sideband.c | 17 +++++++++++++++-- + t/t5409-colorize-remote-messages.sh | 12 ++++++++++++ + 2 files changed, 27 insertions(+), 2 deletions(-) + +diff --git a/sideband.c b/sideband.c +index 85bddfdcd4f57a..9384cb02d56a04 100644 +--- a/sideband.c ++++ b/sideband.c +@@ -61,6 +61,19 @@ void list_config_color_sideband_slots(struct string_list *list, const char *pref + list_config_item(list, prefix, keywords[i].keyword); + } + ++static void strbuf_add_sanitized(struct strbuf *dest, const char *src, int n) ++{ ++ strbuf_grow(dest, n); ++ for (; n && *src; src++, n--) { ++ if (!iscntrl(*src) || *src == '\t' || *src == '\n') ++ strbuf_addch(dest, *src); ++ else { ++ strbuf_addch(dest, '^'); ++ strbuf_addch(dest, 0x40 + *src); ++ } ++ } ++} ++ + /* + * Optionally highlight one keyword in remote output if it appears at the start + * of the line. This should be called for a single line only, which is +@@ -73,7 +86,7 @@ static void maybe_colorize_sideband(struct strbuf *dest, const char *src, int n) + int i; + + if (!want_color_stderr(use_sideband_colors())) { +- strbuf_add(dest, src, n); ++ strbuf_add_sanitized(dest, src, n); + return; + } + +@@ -106,7 +119,7 @@ static void maybe_colorize_sideband(struct strbuf *dest, const char *src, int n) + } + } + +- strbuf_add(dest, src, n); ++ strbuf_add_sanitized(dest, src, n); + } + + +diff --git a/t/t5409-colorize-remote-messages.sh b/t/t5409-colorize-remote-messages.sh +index fa5de4500a4f50..6a6e0d15b21050 100755 +--- a/t/t5409-colorize-remote-messages.sh ++++ b/t/t5409-colorize-remote-messages.sh +@@ -98,4 +98,16 @@ test_expect_success 'fallback to color.ui' ' + grep "error: error" decoded + ' + ++test_expect_success 'disallow (color) control sequences in sideband' ' ++ write_script .git/color-me-surprised <<-\EOF && ++ printf "error: Have you \\033[31mread\\033[m this?\\n" >&2 ++ exec "$@" ++ EOF ++ test_config_global uploadPack.packObjectshook ./color-me-surprised && ++ test_commit need-at-least-one-commit && ++ git clone --no-local . throw-away 2>stderr && ++ test_decode_color decoded && ++ test_i18ngrep ! RED decoded ++' ++ + test_done + +From a8c289b0a531d25336a96eaa5e3584414ed4c6c4 Mon Sep 17 00:00:00 2001 +From: Johannes Schindelin +Date: Wed, 6 Nov 2024 21:07:51 +0100 +Subject: [PATCH 2/3] sideband: introduce an "escape hatch" to allow control + characters + +The preceding commit fixed the vulnerability whereas sideband messages +(that are under the control of the remote server) could contain ANSI +escape sequences that would be sent to the terminal verbatim. + +However, this fix may not be desirable under all circumstances, e.g. +when remote servers deliberately add coloring to their messages to +increase their urgency. + +To help with those use cases, give users a way to opt-out of the +protections: `sideband.allowControlCharacters`. + +Signed-off-by: Johannes Schindelin +--- + Documentation/config.txt | 2 ++ + Documentation/config/sideband.txt | 5 +++++ + sideband.c | 10 ++++++++++ + t/t5409-colorize-remote-messages.sh | 8 +++++++- + 4 files changed, 24 insertions(+), 1 deletion(-) + create mode 100644 Documentation/config/sideband.txt + +diff --git a/Documentation/config.txt b/Documentation/config.txt +index 0e93aef86264db..abdbfba9bd756a 100644 +--- a/Documentation/config.txt ++++ b/Documentation/config.txt +@@ -511,6 +511,8 @@ include::config/sequencer.txt[] + + include::config/showbranch.txt[] + ++include::config/sideband.txt[] ++ + include::config/sparse.txt[] + + include::config/splitindex.txt[] +diff --git a/Documentation/config/sideband.txt b/Documentation/config/sideband.txt +new file mode 100644 +index 00000000000000..3fb5045cd79581 +--- /dev/null ++++ b/Documentation/config/sideband.txt +@@ -0,0 +1,5 @@ ++sideband.allowControlCharacters:: ++ By default, control characters that are delivered via the sideband ++ are masked, to prevent potentially unwanted ANSI escape sequences ++ from being sent to the terminal. Use this config setting to override ++ this behavior. +diff --git a/sideband.c b/sideband.c +index 9384cb02d56a04..8ebf1f0743e6b6 100644 +--- a/sideband.c ++++ b/sideband.c +@@ -20,6 +20,8 @@ static struct keyword_entry keywords[] = { + { "error", GIT_COLOR_BOLD_RED }, + }; + ++static int allow_control_characters; ++ + /* Returns a color setting (GIT_COLOR_NEVER, etc). */ + static int use_sideband_colors(void) + { +@@ -33,6 +35,9 @@ static int use_sideband_colors(void) + if (use_sideband_colors_cached >= 0) + return use_sideband_colors_cached; + ++ git_config_get_bool("sideband.allowcontrolcharacters", ++ &allow_control_characters); ++ + if (!git_config_get_string(key, &value)) { + use_sideband_colors_cached = git_config_colorbool(key, value); + } else if (!git_config_get_string("color.ui", &value)) { +@@ -63,6 +68,11 @@ void list_config_color_sideband_slots(struct string_list *list, const char *pref + + static void strbuf_add_sanitized(struct strbuf *dest, const char *src, int n) + { ++ if (allow_control_characters) { ++ strbuf_add(dest, src, n); ++ return; ++ } ++ + strbuf_grow(dest, n); + for (; n && *src; src++, n--) { + if (!iscntrl(*src) || *src == '\t' || *src == '\n') +diff --git a/t/t5409-colorize-remote-messages.sh b/t/t5409-colorize-remote-messages.sh +index 6a6e0d15b21050..1cd0640f200009 100755 +--- a/t/t5409-colorize-remote-messages.sh ++++ b/t/t5409-colorize-remote-messages.sh +@@ -105,9 +105,15 @@ test_expect_success 'disallow (color) control sequences in sideband' ' + EOF + test_config_global uploadPack.packObjectshook ./color-me-surprised && + test_commit need-at-least-one-commit && ++ + git clone --no-local . throw-away 2>stderr && + test_decode_color decoded && +- test_i18ngrep ! RED decoded ++ test_i18ngrep ! RED decoded && ++ ++ rm -rf throw-away && ++ git -c sideband.allowControlCharacters clone --no-local . throw-away 2>stderr && ++ test_decode_color decoded && ++ test_i18ngrep RED decoded + ' + + test_done + +From c7049c2a7f47c99a67fd869f1ee89d7aa1a328d2 Mon Sep 17 00:00:00 2001 +From: Johannes Schindelin +Date: Mon, 18 Nov 2024 21:42:57 +0100 +Subject: [PATCH 3/3] sideband: do allow ANSI color sequences by default + +The preceding two commits introduced special handling of the sideband +channel to neutralize ANSI escape sequences before sending the payload +to the terminal, and `sideband.allowControlCharacters` to override that +behavior. + +However, some `pre-receive` hooks that are actively used in practice +want to color their messages and therefore rely on the fact that Git +passes them through to the terminal. + +In contrast to other ANSI escape sequences, it is highly unlikely that +coloring sequences can be essential tools in attack vectors that mislead +Git users e.g. by hiding crucial information. + +Therefore we can have both: Continue to allow ANSI coloring sequences to +be passed to the terminal, and neutralize all other ANSI escape +sequences. + +Signed-off-by: Johannes Schindelin +--- + Documentation/config/sideband.txt | 17 ++++++-- + sideband.c | 61 ++++++++++++++++++++++++++--- + t/t5409-colorize-remote-messages.sh | 16 +++++++- + 3 files changed, 84 insertions(+), 10 deletions(-) + +diff --git a/Documentation/config/sideband.txt b/Documentation/config/sideband.txt +index 3fb5045cd79581..f347fd6b33004a 100644 +--- a/Documentation/config/sideband.txt ++++ b/Documentation/config/sideband.txt +@@ -1,5 +1,16 @@ + sideband.allowControlCharacters:: + By default, control characters that are delivered via the sideband +- are masked, to prevent potentially unwanted ANSI escape sequences +- from being sent to the terminal. Use this config setting to override +- this behavior. ++ are masked, except ANSI color sequences. This prevents potentially ++ unwanted ANSI escape sequences from being sent to the terminal. Use ++ this config setting to override this behavior: +++ ++-- ++ color:: ++ Allow ANSI color sequences, line feeds and horizontal tabs, ++ but mask all other control characters. This is the default. ++ false:: ++ Mask all control characters other than line feeds and ++ horizontal tabs. ++ true:: ++ Allow all control characters to be sent to the terminal. ++-- +diff --git a/sideband.c b/sideband.c +index 8ebf1f0743e6b6..afd62aa008154b 100644 +--- a/sideband.c ++++ b/sideband.c +@@ -20,7 +20,11 @@ static struct keyword_entry keywords[] = { + { "error", GIT_COLOR_BOLD_RED }, + }; + +-static int allow_control_characters; ++static enum { ++ ALLOW_NO_CONTROL_CHARACTERS = 0, ++ ALLOW_ALL_CONTROL_CHARACTERS = 1, ++ ALLOW_ANSI_COLOR_SEQUENCES = 2 ++} allow_control_characters = ALLOW_ANSI_COLOR_SEQUENCES; + + /* Returns a color setting (GIT_COLOR_NEVER, etc). */ + static int use_sideband_colors(void) +@@ -35,8 +39,24 @@ static int use_sideband_colors(void) + if (use_sideband_colors_cached >= 0) + return use_sideband_colors_cached; + +- git_config_get_bool("sideband.allowcontrolcharacters", +- &allow_control_characters); ++ switch (git_config_get_maybe_bool("sideband.allowcontrolcharacters", &i)) { ++ case 0: /* Boolean value */ ++ allow_control_characters = i ? ALLOW_ALL_CONTROL_CHARACTERS : ++ ALLOW_NO_CONTROL_CHARACTERS; ++ break; ++ case -1: /* non-Boolean value */ ++ if (git_config_get_string("sideband.allowcontrolcharacters", ++ &value)) ++ ; /* huh? `get_maybe_bool()` returned -1 */ ++ else if (!strcmp(value, "color")) ++ allow_control_characters = ALLOW_ANSI_COLOR_SEQUENCES; ++ else ++ warning(_("unrecognized value for `sideband." ++ "allowControlCharacters`: '%s'"), value); ++ break; ++ default: ++ break; /* not configured */ ++ } + + if (!git_config_get_string(key, &value)) { + use_sideband_colors_cached = git_config_colorbool(key, value); +@@ -66,9 +86,37 @@ void list_config_color_sideband_slots(struct string_list *list, const char *pref + list_config_item(list, prefix, keywords[i].keyword); + } + ++static int handle_ansi_color_sequence(struct strbuf *dest, const char *src, int n) ++{ ++ int i; ++ ++ /* ++ * Valid ANSI color sequences are of the form ++ * ++ * ESC [ [ [; ]*] m ++ */ ++ ++ if (allow_control_characters != ALLOW_ANSI_COLOR_SEQUENCES || ++ n < 3 || src[0] != '\x1b' || src[1] != '[') ++ return 0; ++ ++ for (i = 2; i < n; i++) { ++ if (src[i] == 'm') { ++ strbuf_add(dest, src, i + 1); ++ return i; ++ } ++ if (!isdigit(src[i]) && src[i] != ';') ++ break; ++ } ++ ++ return 0; ++} ++ + static void strbuf_add_sanitized(struct strbuf *dest, const char *src, int n) + { +- if (allow_control_characters) { ++ int i; ++ ++ if (allow_control_characters == ALLOW_ALL_CONTROL_CHARACTERS) { + strbuf_add(dest, src, n); + return; + } +@@ -77,7 +125,10 @@ static void strbuf_add_sanitized(struct strbuf *dest, const char *src, int n) + for (; n && *src; src++, n--) { + if (!iscntrl(*src) || *src == '\t' || *src == '\n') + strbuf_addch(dest, *src); +- else { ++ else if ((i = handle_ansi_color_sequence(dest, src, n))) { ++ src += i; ++ n -= i; ++ } else { + strbuf_addch(dest, '^'); + strbuf_addch(dest, 0x40 + *src); + } +diff --git a/t/t5409-colorize-remote-messages.sh b/t/t5409-colorize-remote-messages.sh +index 1cd0640f200009..43296ea51c5db1 100755 +--- a/t/t5409-colorize-remote-messages.sh ++++ b/t/t5409-colorize-remote-messages.sh +@@ -100,7 +100,7 @@ test_expect_success 'fallback to color.ui' ' + + test_expect_success 'disallow (color) control sequences in sideband' ' + write_script .git/color-me-surprised <<-\EOF && +- printf "error: Have you \\033[31mread\\033[m this?\\n" >&2 ++ printf "error: Have you \\033[31mread\\033[m this?\\a\\n" >&2 + exec "$@" + EOF + test_config_global uploadPack.packObjectshook ./color-me-surprised && +@@ -108,12 +108,24 @@ test_expect_success 'disallow (color) control sequences in sideband' ' + + git clone --no-local . throw-away 2>stderr && + test_decode_color decoded && ++ test_i18ngrep RED decoded && ++ test_i18ngrep "\\^G" stderr && ++ tr -dc "\\007" actual && ++ test_must_be_empty actual && ++ ++ rm -rf throw-away && ++ git -c sideband.allowControlCharacters=false \ ++ clone --no-local . throw-away 2>stderr && ++ test_decode_color decoded && + test_i18ngrep ! RED decoded && ++ test_i18ngrep "\\^G" stderr && + + rm -rf throw-away && + git -c sideband.allowControlCharacters clone --no-local . throw-away 2>stderr && + test_decode_color decoded && +- test_i18ngrep RED decoded ++ test_i18ngrep RED decoded && ++ tr -dc "\\007" actual && ++ test_file_not_empty actual + ' + + test_done diff --git a/backport-CVE-2024-52006-credential-disallow-Carriage-Returns-in-the-protocol.patch b/backport-CVE-2024-52006-credential-disallow-Carriage-Returns-in-the-protocol.patch new file mode 100644 index 0000000000000000000000000000000000000000..b1f2edd5a84748f0ff11f352efe6ca4d8d7b36b3 --- /dev/null +++ b/backport-CVE-2024-52006-credential-disallow-Carriage-Returns-in-the-protocol.patch @@ -0,0 +1,171 @@ +From b01b9b81d36759cdcd07305e78765199e1bc2060 Mon Sep 17 00:00:00 2001 +From: Johannes Schindelin +Date: Mon, 4 Nov 2024 14:48:22 +0100 +Subject: [PATCH] credential: disallow Carriage Returns in the protocol by + default + +While Git has documented that the credential protocol is line-based, +with newlines as terminators, the exact shape of a newline has not been +documented. + +From Git's perspective, which is firmly rooted in the Linux ecosystem, +it is clear that "a newline" means a Line Feed character. + +However, even Git's credential protocol respects Windows line endings +(a Carriage Return character followed by a Line Feed character, "CR/LF") +by virtue of using `strbuf_getline()`. + +There is a third category of line endings that has been used originally +by MacOS, and that is respected by the default line readers of .NET and +node.js: bare Carriage Returns. + +Git cannot handle those, and what is worse: Git's remedy against +CVE-2020-5260 does not catch when credential helpers are used that +interpret bare Carriage Returns as newlines. + +Git Credential Manager addressed this as CVE-2024-50338, but other +credential helpers may still be vulnerable. So let's not only disallow +Line Feed characters as part of the values in the credential protocol, +but also disallow Carriage Return characters. + +In the unlikely event that a credential helper relies on Carriage +Returns in the protocol, introduce an escape hatch via the +`credential.protectProtocol` config setting. + +This addresses CVE-2024-52006. + +Signed-off-by: Johannes Schindelin +--- + Documentation/config/credential.txt | 5 +++++ + credential.c | 21 ++++++++++++++------- + credential.h | 4 +++- + t/t0300-credentials.sh | 16 ++++++++++++++++ + 4 files changed, 38 insertions(+), 8 deletions(-) + +diff --git a/Documentation/config/credential.txt b/Documentation/config/credential.txt +index fd8113d6..9cadca7f 100644 +--- a/Documentation/config/credential.txt ++++ b/Documentation/config/credential.txt +@@ -20,6 +20,11 @@ credential.sanitizePrompt:: + will be URL-encoded by default). Configure this setting to `false` to + override that behavior. + ++credential.protectProtocol:: ++ By default, Carriage Return characters are not allowed in the protocol ++ that is used when Git talks to a credential helper. This setting allows ++ users to override this default. ++ + credential.username:: + If no username is set for a network authentication, use this username + by default. See credential..* below, and +diff --git a/credential.c b/credential.c +index 1392a54d..b76a7309 100644 +--- a/credential.c ++++ b/credential.c +@@ -69,6 +69,8 @@ static int credential_config_callback(const char *var, const char *value, + c->use_http_path = git_config_bool(var, value); + else if (!strcmp(key, "sanitizeprompt")) + c->sanitize_prompt = git_config_bool(var, value); ++ else if (!strcmp(key, "protectprotocol")) ++ c->protect_protocol = git_config_bool(var, value); + + return 0; + } +@@ -262,7 +264,8 @@ int credential_read(struct credential *c, FILE *fp) + return 0; + } + +-static void credential_write_item(FILE *fp, const char *key, const char *value, ++static void credential_write_item(const struct credential *c, ++ FILE *fp, const char *key, const char *value, + int required) + { + if (!value && required) +@@ -271,24 +274,28 @@ static void credential_write_item(FILE *fp, const char *key, const char *value, + return; + if (strchr(value, '\n')) + die("credential value for %s contains newline", key); ++ if (c->protect_protocol && strchr(value, '\r')) ++ die("credential value for %s contains carriage return\n" ++ "If this is intended, set `credential.protectProtocol=false`", ++ key); + fprintf(fp, "%s=%s\n", key, value); + } + + void credential_write(const struct credential *c, FILE *fp) + { +- credential_write_item(fp, "protocol", c->protocol, 1); +- credential_write_item(fp, "host", c->host, 1); +- credential_write_item(fp, "path", c->path, 0); +- credential_write_item(fp, "username", c->username, 0); +- credential_write_item(fp, "password", c->password, 0); +- credential_write_item(fp, "oauth_refresh_token", c->oauth_refresh_token, 0); ++ credential_write_item(c, fp, "protocol", c->protocol, 1); ++ credential_write_item(c, fp, "host", c->host, 1); ++ credential_write_item(c, fp, "path", c->path, 0); ++ credential_write_item(c, fp, "username", c->username, 0); ++ credential_write_item(c, fp, "password", c->password, 0); ++ credential_write_item(c, fp, "oauth_refresh_token", c->oauth_refresh_token, 0); + if (c->password_expiry_utc != TIME_MAX) { + char *s = xstrfmt("%"PRItime, c->password_expiry_utc); +- credential_write_item(fp, "password_expiry_utc", s, 0); ++ credential_write_item(c, fp, "password_expiry_utc", s, 0); + free(s); + } + for (size_t i = 0; i < c->wwwauth_headers.nr; i++) +- credential_write_item(fp, "wwwauth[]", c->wwwauth_headers.v[i], 0); ++ credential_write_item(c, fp, "wwwauth[]", c->wwwauth_headers.v[i], 0); + } + + static int run_credential_helper(struct credential *c, +diff --git a/credential.h b/credential.h +index 0364d436..2c0b39a9 100644 +--- a/credential.h ++++ b/credential.h +@@ -120,7 +120,8 @@ struct credential { + quit:1, + use_http_path:1, + username_from_proto:1, +- sanitize_prompt:1; ++ sanitize_prompt:1, ++ protect_protocol:1; + + char *username; + char *password; +@@ -134,6 +135,7 @@ struct credential { + .password_expiry_utc = TIME_MAX, \ + .wwwauth_headers = STRVEC_INIT, \ + .sanitize_prompt = 1, \ ++ .protect_protocol = 1, \ + } + + /* Initialize a credential structure, setting all fields to empty. */ +diff --git a/t/t0300-credentials.sh b/t/t0300-credentials.sh +index b62c70c1..168ae765 100755 +--- a/t/t0300-credentials.sh ++++ b/t/t0300-credentials.sh +@@ -720,6 +720,22 @@ test_expect_success 'url parser rejects embedded newlines' ' + test_cmp expect stderr + ' + ++test_expect_success 'url parser rejects embedded carriage returns' ' ++ test_config credential.helper "!true" && ++ test_must_fail git credential fill 2>stderr <<-\EOF && ++ url=https://example%0d.com/ ++ EOF ++ cat >expect <<-\EOF && ++ fatal: credential value for host contains carriage return ++ If this is intended, set `credential.protectProtocol=false` ++ EOF ++ test_cmp expect stderr && ++ GIT_ASKPASS=true \ ++ git -c credential.protectProtocol=false credential fill <<-\EOF ++ url=https://example%0d.com/ ++ EOF ++' ++ + test_expect_success 'host-less URLs are parsed as empty host' ' + check fill "verbatim foo bar" <<-\EOF + url=cert:///path/to/cert.pem +-- +2.23.0 diff --git a/backport-CVE-2025-48384-config-quote-values-containing-CR-character.patch b/backport-CVE-2025-48384-config-quote-values-containing-CR-character.patch new file mode 100644 index 0000000000000000000000000000000000000000..5f7641da4b5c2e4d17a032587d367047354c33da --- /dev/null +++ b/backport-CVE-2025-48384-config-quote-values-containing-CR-character.patch @@ -0,0 +1,123 @@ +From 05e9cd64ee23bbadcea6bcffd6660ed02b8eab89 Mon Sep 17 00:00:00 2001 +From: Justin Tobler +Date: Mon, 19 May 2025 21:26:04 -0500 +Subject: [PATCH] config: quote values containing CR character + +When reading the config, values that contain a trailing CRLF are +stripped. If the value itself has a trailing CR, the normal LF that +follows results in the CR being unintentionally stripped. This may lead +to unintended behavior due to the config value written being different +when it gets read. + +One such issue involves a repository with a submodule path containing a +trailing CR. When the submodule gets initialized, the submodule is +cloned without being checked out and has "core.worktree" set to the +submodule path. The git-checkout(1) that gets spawned later reads the +"core.worktree" config value, but without the trailing CR, and +consequently attempts to checkout to a different path than intended. + +If the repository contains a matching path that is a symlink, it is +possible for the submodule repository to be checked out in arbitrary +locations. This is extra bad when the symlink points to the submodule +hooks directory and the submodule repository contains an executable +"post-checkout" hook. Once the submodule repository checkout completes, +the "post-checkout" hook immediately executes. + +To prevent mismatched config state due to misinterpreting a trailing CR, +wrap config values containing CR in double quotes when writing the +entry. This ensures a trailing CR is always separated for an LF and thus +prevented from getting stripped. + +Note that this problem cannot be addressed by just quoting each CR with +"\r". The reading side of the config interprets only a few backslash +escapes, and "\r" is not among them. This fix is sufficient though +because it only affects the CR at the end of a line and any literal CR +in the interior is already preserved. + +Co-authored-by: David Leadbeater +Signed-off-by: Justin Tobler +Signed-off-by: Taylor Blau +--- + config.c | 2 +- + t/t1300-config.sh | 11 +++++++++++ + t/t7450-bad-git-dotfiles.sh | 33 +++++++++++++++++++++++++++++++++ + 3 files changed, 45 insertions(+), 1 deletion(-) + +diff --git a/config.c b/config.c +index 9ff6ae1cb9..629981451d 100644 +--- a/config.c ++++ b/config.c +@@ -2999,7 +2999,7 @@ static ssize_t write_pair(int fd, const char *key, const char *value, + if (value[0] == ' ') + quote = "\""; + for (i = 0; value[i]; i++) +- if (value[i] == ';' || value[i] == '#') ++ if (value[i] == ';' || value[i] == '#' || value[i] == '\r') + quote = "\""; + if (i && value[i - 1] == ' ') + quote = "\""; +diff --git a/t/t1300-config.sh b/t/t1300-config.sh +index f4e2752134..1010410b7e 100755 +--- a/t/t1300-config.sh ++++ b/t/t1300-config.sh +@@ -2590,4 +2590,15 @@ test_expect_success 'includeIf.hasconfig:remote.*.url forbids remote url in such included files' ' + grep "fatal: remote URLs cannot be configured in file directly or indirectly included by includeIf.hasconfig:remote.*.url" err + ' + ++test_expect_success 'writing value with trailing CR not stripped on read' ' ++ test_when_finished "rm -rf cr-test" && ++ ++ printf "bar\r\n" >expect && ++ git init cr-test && ++ git -C cr-test config --add core.foo $(printf "bar\r") && ++ git -C cr-test config --get core.foo >actual && ++ ++ test_cmp expect actual ++' ++ + test_done +diff --git a/t/t7450-bad-git-dotfiles.sh b/t/t7450-bad-git-dotfiles.sh +index 5b845e899b..2026285566 100755 +--- a/t/t7450-bad-git-dotfiles.sh ++++ b/t/t7450-bad-git-dotfiles.sh +@@ -347,4 +347,37 @@ test_expect_success 'git dirs of sibling submodules must not be nested' ' + test_grep "is inside git dir" err + ' + ++test_expect_success SYMLINKS,!WINDOWS,!MINGW 'submodule must not checkout into different directory' ' ++ test_when_finished "rm -rf sub repo bad-clone" && ++ ++ git init sub && ++ write_script sub/post-checkout <<-\EOF && ++ touch "$PWD/foo" ++ EOF ++ git -C sub add post-checkout && ++ git -C sub commit -m hook && ++ ++ git init repo && ++ git -C repo -c protocol.file.allow=always submodule add "$PWD/sub" sub && ++ git -C repo mv sub $(printf "sub\r") && ++ ++ # Ensure config values containing CR are wrapped in quotes. ++ git config --unset -f repo/.gitmodules submodule.sub.path && ++ printf "\tpath = \"sub\r\"\n" >>repo/.gitmodules && ++ ++ git config --unset -f repo/.git/modules/sub/config core.worktree && ++ { ++ printf "[core]\n" && ++ printf "\tworktree = \"../../../sub\r\"\n" ++ } >>repo/.git/modules/sub/config && ++ ++ ln -s .git/modules/sub/hooks repo/sub && ++ git -C repo add -A && ++ git -C repo commit -m submodule && ++ ++ git -c protocol.file.allow=always clone --recurse-submodules repo bad-clone && ++ ! test -f "$PWD/foo" && ++ test -f $(printf "bad-clone/sub\r/post-checkout") ++' ++ + test_done +-- +2.33.0 + diff --git a/backport-CVE-2025-48385-bundle-uri-fix-arbitrary-file-writes-via-parameter-i.patch b/backport-CVE-2025-48385-bundle-uri-fix-arbitrary-file-writes-via-parameter-i.patch new file mode 100644 index 0000000000000000000000000000000000000000..60afc4422a2ce8538833dd7ea05ab63dc91a15f6 --- /dev/null +++ b/backport-CVE-2025-48385-bundle-uri-fix-arbitrary-file-writes-via-parameter-i.patch @@ -0,0 +1,160 @@ +From 35cb1bb0b92c132249d932c05bbd860d410e12d4 Mon Sep 17 00:00:00 2001 +From: Patrick Steinhardt' via Git Security +Date: Wed, 14 May 2025 08:32:02 +0200 +Subject: [PATCH] bundle-uri: fix arbitrary file writes via parameter injection + +We fetch bundle URIs via `download_https_uri_to_file()`. The logic to +fetch those bundles is not handled in-process, but we instead use a +separate git-remote-https(1) process that performs the fetch for us. The +information about which file should be downloaded and where that file +should be put gets communicated via stdin of that process via a "get" +request. This "get" request has the form "get $uri $file\n\n". As may be +obvious to the reader, this will cause git-remote-https(1) to download +the URI "$uri" and put it into "$file". + +The fact that we are using plain spaces and newlines as separators for +the request arguments means that we have to be extra careful with the +respective vaules of these arguments: + + - If "$uri" contained a space we would interpret this as both URI and + target location. + + - If either "$uri" or "$file" contained a newline we would interpret + this as a new command. + +But we neither quote the arguments such that any characters with special +meaning would be escaped, nor do we verify that none of these special +characters are contained. + +If either the URI or file contains a newline character, we are open to +protocol injection attacks. Likewise, if the URI itself contains a +space, then an attacker-controlled URI can lead to partially-controlled +file writes. + +Note that the attacker-controlled URIs do not permit completely +arbitrary file writes, but instead allows an attacker to control the +path in which we will write a temporary (e.g., "tmp_uri_XXXXXX") +file. + +The result is twofold: + + - By adding a space in "$uri" we can control where exactly a file will + be written to, including out-of-repository writes. The final + location is not completely arbitrary, as the injected string will be + concatenated with the original "$file" path. Furthermore, the name + of the bundle will be "tmp_uri_XXXXXX", further restricting what an + adversary would be able to write. + + Also note that is not possible for the URI to contain a newline + because we end up in `credential_from_url_1()` before we try to + issue any requests using that URI. As such, it is not possible to + inject arbitrary commands via the URI. + + - By adding a newline to "$file" we can inject arbitrary commands. + This gives us full control over where a specific file will be + written to. Potential attack vectors would be to overwrite hooks, + but if an adversary were to guess where the user's home directory is + located they might also easily write e.g. a "~/.profile" file and + thus cause arbitrary code execution. + + This injection can only become possible when the adversary has full + control over the target path where a bundle will be downloaded to. + While this feels unlikely, it is possible to control this path when + users perform a recursive clone with a ".gitmodules" file that is + controlled by the adversary. + +Luckily though, the use of bundle URIs is not enabled by default in Git +clients (yet): they have to be enabled by setting the `bundle.heuristic` +config key explicitly. As such, the blast radius of this parameter +injection should overall be quite contained. + +Fix the issue by rejecting spaces in the URI and newlines in both the +URI and the file. As explained, it shouldn't be required to also +restrict the use of newlines in the URI, as we would eventually die +anyway in `credential_from_url_1()`. But given that we're only one small +step away from arbitrary code execution, let's rather be safe and +restrict newlines in URIs, as well. + +Eventually we should probably refactor the way that Git talks with the +git-remote-https(1) subprocess so that it is less fragile. Until then, +these two restrictions should plug the issue. + +Reported-by: David Leadbeater +Based-on-patch-by: David Leadbeater +Signed-off-by: Patrick Steinhardt +Signed-off-by: Taylor Blau +--- + bundle-uri.c | 22 ++++++++++++++++++++++ + t/t5558-clone-bundle-uri.sh | 23 +++++++++++++++++++++++ + 2 files changed, 45 insertions(+) + +diff --git a/bundle-uri.c b/bundle-uri.c +index ca32050a78..a6a3c1c4b3 100644 +--- a/bundle-uri.c ++++ b/bundle-uri.c +@@ -292,6 +292,28 @@ static int download_https_uri_to_file(const char *file, const char *uri) + struct strbuf line = STRBUF_INIT; + int found_get = 0; + ++ /* ++ * The protocol we speak with git-remote-https(1) uses a space to ++ * separate between URI and file, so the URI itself must not contain a ++ * space. If it did, an adversary could change the location where the ++ * downloaded file is being written to. ++ * ++ * Similarly, we use newlines to separate commands from one another. ++ * Consequently, neither the URI nor the file must contain a newline or ++ * otherwise an adversary could inject arbitrary commands. ++ * ++ * TODO: Restricting newlines in the target paths may break valid ++ * usecases, even if those are a bit more on the esoteric side. ++ * If this ever becomes a problem we should probably think about ++ * alternatives. One alternative could be to use NUL-delimited ++ * requests in git-remote-http(1). Another alternative could be ++ * to use URL quoting. ++ */ ++ if (strpbrk(uri, " \n")) ++ return error("bundle-uri: URI is malformed: '%s'", file); ++ if (strchr(file, '\n')) ++ return error("bundle-uri: filename is malformed: '%s'", file); ++ + strvec_pushl(&cp.args, "git-remote-https", uri, NULL); + cp.err = -1; + cp.in = -1; +diff --git a/t/t5558-clone-bundle-uri.sh b/t/t5558-clone-bundle-uri.sh +index 996a08e90c..2af523aaa4 100755 +--- a/t/t5558-clone-bundle-uri.sh ++++ b/t/t5558-clone-bundle-uri.sh +@@ -1052,6 +1052,29 @@ test_expect_success 'bundles are downloaded once during fetch --all' ' + trace-mult.txt >bundle-fetches && + test_line_count = 1 bundle-fetches + ' ++ ++test_expect_success 'bundles with space in URI are rejected' ' ++ test_when_finished "rm -rf busted repo" && ++ mkdir -p "$HOME/busted/ /$HOME/repo/.git/objects/bundles" && ++ git clone --bundle-uri="$HTTPD_URL/bogus $HOME/busted/" "$HTTPD_URL/smart/fetch.git" repo 2>err && ++ test_grep "error: bundle-uri: URI is malformed: " err && ++ find busted -type f >files && ++ test_must_be_empty files ++' ++ ++test_expect_success 'bundles with newline in URI are rejected' ' ++ test_when_finished "rm -rf busted repo" && ++ git clone --bundle-uri="$HTTPD_URL/bogus\nget $HTTPD_URL/bogus $HOME/busted" "$HTTPD_URL/smart/fetch.git" repo 2>err && ++ test_grep "error: bundle-uri: URI is malformed: " err && ++ test_path_is_missing "$HOME/busted" ++' ++ ++test_expect_success 'bundles with newline in target path are rejected' ' ++ git clone --bundle-uri="$HTTPD_URL/bogus" "$HTTPD_URL/smart/fetch.git" "$(printf "escape\nget $HTTPD_URL/bogus .")" 2>err && ++ test_grep "error: bundle-uri: filename is malformed: " err && ++ test_path_is_missing escape ++' ++ + # Do not add tests here unless they use the HTTP server, as they will + # not run unless the HTTP dependencies exist. + +-- +2.33.0 + diff --git a/backport-CVE-2025-48386-wincred-avoid-buffer-overflow-in-wcsncat.patch b/backport-CVE-2025-48386-wincred-avoid-buffer-overflow-in-wcsncat.patch new file mode 100644 index 0000000000000000000000000000000000000000..8e993aeef37172579f26e908bad0ad2cc2acb8af --- /dev/null +++ b/backport-CVE-2025-48386-wincred-avoid-buffer-overflow-in-wcsncat.patch @@ -0,0 +1,93 @@ +From 9de345cb273cc7faaeda279c7e07149d8a15a319 Mon Sep 17 00:00:00 2001 +From: Taylor Blau +Date: Mon, 19 May 2025 18:30:29 -0400 +Subject: [PATCH] wincred: avoid buffer overflow in wcsncat() + +The wincred credential helper uses a static buffer ("target") as a +unique key for storing and comparing against internal storage. It does +this by building up a string is supposed to look like: + + git:$PROTOCOL://$USERNAME@$HOST/@PATH + +However, the static "target" buffer is declared as a wide string with no +more than 1,024 wide characters. The first call to wcsncat() is almost +correct (it copies no more than ARRAY_SIZE(target) wchar_t's), but does +not account for the trailing NUL, introducing an off-by-one error. + +But subsequent calls to wcsncat() have an additional problem on top of +the off-by-one. They do not account for the length of the existing +wide string being built up in 'target'. So the following: + + $ perl -e ' + my $x = "x" x 1_000; + print "protocol=$x\nhost=$x\nusername=$x\npath=$x\n" + ' | + C\:/Program\ Files/Git/mingw64/libexec/git-core/git-credential-wincred.exe get + +will result in a segmentation fault from over-filling buffer. + +This bug is as old as the wincred helper itself, dating back to +a6253da0f3 (contrib: add win32 credential-helper, 2012-07-27). Commit +8b2d219a3d (wincred: improve compatibility with windows versions, +2013-01-10) replaced the use of strncat() with wcsncat(), but retained +the buggy behavior. + +Fix this by using a "target_append()" helper which accounts for both the +length of the existing string within the buffer, as well as the trailing +NUL character. + +Reported-by: David Leadbeater +Helped-by: David Leadbeater +Helped-by: Jeff King +Signed-off-by: Taylor Blau +--- + .../wincred/git-credential-wincred.c | 22 +++++++++++++------ + 1 file changed, 15 insertions(+), 7 deletions(-) + +diff --git a/contrib/credential/wincred/git-credential-wincred.c b/contrib/credential/wincred/git-credential-wincred.c +index 4cd56c42e2..ceff44207a 100644 +--- a/contrib/credential/wincred/git-credential-wincred.c ++++ b/contrib/credential/wincred/git-credential-wincred.c +@@ -37,6 +37,14 @@ static void *xmalloc(size_t size) + static WCHAR *wusername, *password, *protocol, *host, *path, target[1024], + *password_expiry_utc; + ++static void target_append(const WCHAR *src) ++{ ++ size_t avail = ARRAY_SIZE(target) - wcslen(target) - 1; /* -1 for NUL */ ++ if (avail < wcslen(src)) ++ die("target buffer overflow"); ++ wcsncat(target, src, avail); ++} ++ + static void write_item(const char *what, LPCWSTR wbuf, int wlen) + { + char *buf; +@@ -294,17 +302,17 @@ int main(int argc, char *argv[]) + + /* prepare 'target', the unique key for the credential */ + wcscpy(target, L"git:"); +- wcsncat(target, protocol, ARRAY_SIZE(target)); +- wcsncat(target, L"://", ARRAY_SIZE(target)); ++ target_append(protocol); ++ target_append(L"://"); + if (wusername) { +- wcsncat(target, wusername, ARRAY_SIZE(target)); +- wcsncat(target, L"@", ARRAY_SIZE(target)); ++ target_append(wusername); ++ target_append(L"@"); + } + if (host) +- wcsncat(target, host, ARRAY_SIZE(target)); ++ target_append(host); + if (path) { +- wcsncat(target, L"/", ARRAY_SIZE(target)); +- wcsncat(target, path, ARRAY_SIZE(target)); ++ target_append(L"/"); ++ target_append(path); + } + + if (!strcmp(argv[1], "get")) +-- +2.33.0 + diff --git a/backport-send-email-avoid-duplicate-specification-warnings.patch b/backport-send-email-avoid-duplicate-specification-warnings.patch new file mode 100644 index 0000000000000000000000000000000000000000..de199c86c9e75764c51c55ef17e4b349344e9f8b --- /dev/null +++ b/backport-send-email-avoid-duplicate-specification-warnings.patch @@ -0,0 +1,116 @@ +From 6ff658cc78f36baa74c0f25314b0043a8f4b4fc6 Mon Sep 17 00:00:00 2001 +From: Todd Zullinger +Date: Thu, 16 Nov 2023 14:30:11 -0500 +Subject: [PATCH] send-email: avoid duplicate specification warnings + +A warning is issued for options which are specified more than once +beginning with perl-Getopt-Long >= 2.55. In addition to causing users +to see warnings, this results in test failures which compare the output. +An example, from t9001-send-email.37: + + | +++ diff -u expect actual + | --- expect 2023-11-14 10:38:23.854346488 +0000 + | +++ actual 2023-11-14 10:38:23.848346466 +0000 + | @@ -1,2 +1,7 @@ + | +Duplicate specification "no-chain-reply-to" for option "no-chain-reply-to" + | +Duplicate specification "to-cover|to-cover!" for option "to-cover" + | +Duplicate specification "cc-cover|cc-cover!" for option "cc-cover" + | +Duplicate specification "no-thread" for option "no-thread" + | +Duplicate specification "no-to-cover" for option "no-to-cover" + | fatal: longline.patch:35 is longer than 998 characters + | warning: no patches were sent + | error: last command exited with $?=1 + | not ok 37 - reject long lines + +Remove the duplicate option specs. These are primarily the explicit +'--no-' prefix opts which were added in f471494303 (git-send-email.perl: +support no- prefix with older GetOptions, 2015-01-30). This was done +specifically to support perl-5.8.0 which includes Getopt::Long 2.32[1]. + +Getopt::Long 2.33 added support for the '--no-' prefix natively by +appending '!' to the option specification string, which was included in +perl-5.8.1 and is not present in perl-5.8.0. The previous commit bumped +the minimum supported Perl version to 5.8.1 so we no longer need to +provide the '--no-' variants for negatable options manually. + +Teach `--git-completion-helper` to output the '--no-' options. They are +not included in the options hash and would otherwise be lost. + +Signed-off-by: Todd Zullinger +Signed-off-by: Junio C Hamano +--- + git-send-email.perl | 19 ++++++------------- + 1 file changed, 6 insertions(+), 13 deletions(-) + +diff --git a/git-send-email.perl b/git-send-email.perl +index 041db702d4..60afafb375 100755 +--- a/git-send-email.perl ++++ b/git-send-email.perl +@@ -119,13 +119,16 @@ sub completion_helper { + + foreach my $key (keys %$original_opts) { + unless (exists $not_for_completion{$key}) { +- $key =~ s/!$//; ++ my $negatable = ($key =~ s/!$//); + + if ($key =~ /[:=][si]$/) { + $key =~ s/[:=][si]$//; + push (@send_email_opts, "--$_=") foreach (split (/\|/, $key)); + } else { + push (@send_email_opts, "--$_") foreach (split (/\|/, $key)); ++ if ($negatable) { ++ push (@send_email_opts, "--no-$_") foreach (split (/\|/, $key)); ++ } + } + } + } +@@ -491,7 +494,6 @@ sub config_regexp { + "bcc=s" => \@getopt_bcc, + "no-bcc" => \$no_bcc, + "chain-reply-to!" => \$chain_reply_to, +- "no-chain-reply-to" => sub {$chain_reply_to = 0}, + "sendmail-cmd=s" => \$sendmail_cmd, + "smtp-server=s" => \$smtp_server, + "smtp-server-option=s" => \@smtp_server_options, +@@ -506,36 +508,27 @@ sub config_regexp { + "smtp-auth=s" => \$smtp_auth, + "no-smtp-auth" => sub {$smtp_auth = 'none'}, + "annotate!" => \$annotate, +- "no-annotate" => sub {$annotate = 0}, + "compose" => \$compose, + "quiet" => \$quiet, + "cc-cmd=s" => \$cc_cmd, + "header-cmd=s" => \$header_cmd, + "no-header-cmd" => \$no_header_cmd, + "suppress-from!" => \$suppress_from, +- "no-suppress-from" => sub {$suppress_from = 0}, + "suppress-cc=s" => \@suppress_cc, + "signed-off-cc|signed-off-by-cc!" => \$signed_off_by_cc, +- "no-signed-off-cc|no-signed-off-by-cc" => sub {$signed_off_by_cc = 0}, +- "cc-cover|cc-cover!" => \$cover_cc, +- "no-cc-cover" => sub {$cover_cc = 0}, +- "to-cover|to-cover!" => \$cover_to, +- "no-to-cover" => sub {$cover_to = 0}, ++ "cc-cover!" => \$cover_cc, ++ "to-cover!" => \$cover_to, + "confirm=s" => \$confirm, + "dry-run" => \$dry_run, + "envelope-sender=s" => \$envelope_sender, + "thread!" => \$thread, +- "no-thread" => sub {$thread = 0}, + "validate!" => \$validate, +- "no-validate" => sub {$validate = 0}, + "transfer-encoding=s" => \$target_xfer_encoding, + "format-patch!" => \$format_patch, +- "no-format-patch" => sub {$format_patch = 0}, + "8bit-encoding=s" => \$auto_8bit_encoding, + "compose-encoding=s" => \$compose_encoding, + "force" => \$force, + "xmailer!" => \$use_xmailer, +- "no-xmailer" => sub {$use_xmailer = 0}, + "batch-size=i" => \$batch_size, + "relogin-delay=i" => \$relogin_delay, + "git-completion-helper" => \$git_completion_helper, +-- +2.33.0 + diff --git a/download b/download deleted file mode 100644 index 0709090be81f8dcab66185d15abd3c199f49b12c..0000000000000000000000000000000000000000 --- a/download +++ /dev/null @@ -1 +0,0 @@ -35401b410e7f248b13e35a1069aca2e2 git-1.8.3.1.tar.gz diff --git a/git-1.5-gitweb-home-link.patch b/git-1.5-gitweb-home-link.patch deleted file mode 100644 index 74c83901fdff3c3c257ae4b3ae5eb924c87e0582..0000000000000000000000000000000000000000 --- a/git-1.5-gitweb-home-link.patch +++ /dev/null @@ -1,12 +0,0 @@ -diff -up git-1.7.2/gitweb/gitweb.perl.orig git-1.7.2/gitweb/gitweb.perl ---- git-1.7.2/gitweb/gitweb.perl.orig 2010-07-21 23:35:25.000000000 +0200 -+++ git-1.7.2/gitweb/gitweb.perl 2010-07-22 10:49:50.385707086 +0200 -@@ -79,7 +79,7 @@ our $projectroot = "++GITWEB_PROJECTROOT - our $project_maxdepth = "++GITWEB_PROJECT_MAXDEPTH++"; - - # string of the home link on top of all pages --our $home_link_str = "++GITWEB_HOME_LINK_STR++"; -+our $home_link_str = $ENV{'SERVER_NAME'} ? "git://" . $ENV{'SERVER_NAME'} : "projects"; - - # name of your site or organization to appear in page titles - # replace this with something more descriptive for clearer bookmarks diff --git a/git-2.17.15-CVE-2020-11008.patch b/git-2.17.15-CVE-2020-11008.patch deleted file mode 100644 index 527261817d09e69bf7d18c2d66d915f8b0b4c7f9..0000000000000000000000000000000000000000 --- a/git-2.17.15-CVE-2020-11008.patch +++ /dev/null @@ -1,1592 +0,0 @@ -From 692455ed04743fbf2c102ae4bcaa0ac7bcc433a0 Mon Sep 17 00:00:00 2001 -From: Christian Couder -Date: Sun, 1 Dec 2013 08:49:16 +0100 -Subject: [PATCH 01/11] strbuf: introduce starts_with() and ends_with() - -prefixcmp() and suffixcmp() share the common "cmp" suffix that -typically are used to name functions that can be used for ordering, -but they can't, because they are not antisymmetric: - - prefixcmp("foo", "foobar") < 0 - prefixcmp("foobar", "foo") == 0 - -We in fact do not use these functions for ordering. Replace them -with functions that just check for equality. - -Add starts_with() and end_with() that will be used to replace -prefixcmp() and suffixcmp(), respectively, as the first step. These -are named after corresponding functions/methods in programming -languages, like Java, Python and Ruby. - -In vcs-svn/fast_export.c, there was already an ends_with() function -that did the same thing. Let's use the new one instead while at it. - -Signed-off-by: Christian Couder -Signed-off-by: Junio C Hamano ---- - git-compat-util.h | 2 ++ - strbuf.c | 18 ++++++++++++++++++ - vcs-svn/fast_export.c | 11 +---------- - 3 files changed, 21 insertions(+), 10 deletions(-) - -diff --git a/git-compat-util.h b/git-compat-util.h -index bd62ab07a8..c4f0afc104 100644 ---- a/git-compat-util.h -+++ b/git-compat-util.h -@@ -341,7 +341,9 @@ extern void set_die_routine(NORETURN_PTR void (*routine)(const char *err, va_lis - extern void set_error_routine(void (*routine)(const char *err, va_list params)); - extern void set_die_is_recursing_routine(int (*routine)(void)); - -+extern int starts_with(const char *str, const char *prefix); - extern int prefixcmp(const char *str, const char *prefix); -+extern int ends_with(const char *str, const char *suffix); - extern int suffixcmp(const char *str, const char *suffix); - - static inline const char *skip_prefix(const char *str, const char *prefix) -diff --git a/strbuf.c b/strbuf.c -index 1170d01c43..83caf4a914 100644 ---- a/strbuf.c -+++ b/strbuf.c -@@ -1,6 +1,15 @@ - #include "cache.h" - #include "refs.h" - -+int starts_with(const char *str, const char *prefix) -+{ -+ for (; ; str++, prefix++) -+ if (!*prefix) -+ return 1; -+ else if (*str != *prefix) -+ return 0; -+} -+ - int prefixcmp(const char *str, const char *prefix) - { - for (; ; str++, prefix++) -@@ -10,6 +19,15 @@ int prefixcmp(const char *str, const char *prefix) - return (unsigned char)*prefix - (unsigned char)*str; - } - -+int ends_with(const char *str, const char *suffix) -+{ -+ int len = strlen(str), suflen = strlen(suffix); -+ if (len < suflen) -+ return 0; -+ else -+ return !strcmp(str + len - suflen, suffix); -+} -+ - int suffixcmp(const char *str, const char *suffix) - { - int len = strlen(str), suflen = strlen(suffix); -diff --git a/vcs-svn/fast_export.c b/vcs-svn/fast_export.c -index f2b23c81de..bd0f2c2b86 100644 ---- a/vcs-svn/fast_export.c -+++ b/vcs-svn/fast_export.c -@@ -162,22 +162,13 @@ static void die_short_read(struct line_buffer *input) - die("invalid dump: unexpected end of file"); - } - --static int ends_with(const char *s, size_t len, const char *suffix) --{ -- const size_t suffixlen = strlen(suffix); -- if (len < suffixlen) -- return 0; -- return !memcmp(s + len - suffixlen, suffix, suffixlen); --} -- - static int parse_cat_response_line(const char *header, off_t *len) - { -- size_t headerlen = strlen(header); - uintmax_t n; - const char *type; - const char *end; - -- if (ends_with(header, headerlen, " missing")) -+ if (ends_with(header, " missing")) - return error("cat-blob reports missing blob: %s", header); - type = strstr(header, " blob "); - if (!type) --- -2.26.2 - - -From 6a8afa912b4d2fe5169fbb904537d1a50a3eb66f Mon Sep 17 00:00:00 2001 -From: Jeff King -Date: Thu, 12 Mar 2020 01:31:11 -0400 -Subject: [PATCH 02/11] credential: detect unrepresentable values when parsing - urls - -The credential protocol can't represent newlines in values, but URLs can -embed percent-encoded newlines in various components. A previous commit -taught the low-level writing routines to die() when encountering this, -but we can be a little friendlier to the user by detecting them earlier -and handling them gracefully. - -This patch teaches credential_from_url() to notice such components, -issue a warning, and blank the credential (which will generally result -in prompting the user for a username and password). We blank the whole -credential in this case. Another option would be to blank only the -invalid component. However, we're probably better off not feeding a -partially-parsed URL result to a credential helper. We don't know how a -given helper would handle it, so we're better off to err on the side of -matching nothing rather than something unexpected. - -The die() call in credential_write() is _probably_ impossible to reach -after this patch. Values should end up in credential structs only by URL -parsing (which is covered here), or by reading credential protocol input -(which by definition cannot read a newline into a value). But we should -definitely keep the low-level check, as it's our final and most accurate -line of defense against protocol injection attacks. Arguably it could -become a BUG(), but it probably doesn't matter much either way. - -Note that the public interface of credential_from_url() grows a little -more than we need here. We'll use the extra flexibility in a future -patch to help fsck catch these cases. ---- - credential.c | 36 ++++++++++++++++++++++++++++++++++-- - credential.h | 16 ++++++++++++++++ - t/t0300-credentials.sh | 12 ++++++++++-- - 3 files changed, 60 insertions(+), 4 deletions(-) - -diff --git a/credential.c b/credential.c -index 096cc5efe9..f56a0a2c02 100644 ---- a/credential.c -+++ b/credential.c -@@ -317,7 +317,22 @@ void credential_reject(struct credential *c) - c->approved = 0; - } - --void credential_from_url(struct credential *c, const char *url) -+static int check_url_component(const char *url, int quiet, -+ const char *name, const char *value) -+{ -+ if (!value) -+ return 0; -+ if (!strchr(value, '\n')) -+ return 0; -+ -+ if (!quiet) -+ warning(_("url contains a newline in its %s component: %s"), -+ name, url); -+ return -1; -+} -+ -+int credential_from_url_gently(struct credential *c, const char *url, -+ int quiet) - { - const char *at, *colon, *cp, *slash, *host, *proto_end; - -@@ -331,7 +346,7 @@ void credential_from_url(struct credential *c, const char *url) - */ - proto_end = strstr(url, "://"); - if (!proto_end) -- return; -+ return 0; - cp = proto_end + 3; - at = strchr(cp, '@'); - colon = strchr(cp, ':'); -@@ -366,4 +381,21 @@ void credential_from_url(struct credential *c, const char *url) - while (p > c->path && *p == '/') - *p-- = '\0'; - } -+ -+ if (check_url_component(url, quiet, "username", c->username) < 0 || -+ check_url_component(url, quiet, "password", c->password) < 0 || -+ check_url_component(url, quiet, "protocol", c->protocol) < 0 || -+ check_url_component(url, quiet, "host", c->host) < 0 || -+ check_url_component(url, quiet, "path", c->path) < 0) -+ return -1; -+ -+ return 0; -+} -+ -+void credential_from_url(struct credential *c, const char *url) -+{ -+ if (credential_from_url_gently(c, url, 0) < 0) { -+ warning(_("skipping credential lookup for url: %s"), url); -+ credential_clear(c); -+ } - } -diff --git a/credential.h b/credential.h -index 0c3e85e8e4..2846e47cc8 100644 ---- a/credential.h -+++ b/credential.h -@@ -27,7 +27,23 @@ void credential_reject(struct credential *); - - int credential_read(struct credential *, FILE *); - void credential_write(const struct credential *, FILE *); -+ -+/* -+ * Parse a url into a credential struct, replacing any existing contents. -+ * -+ * Ifthe url can't be parsed (e.g., a missing "proto://" component), the -+ * resulting credential will be empty but we'll still return success from the -+ * "gently" form. -+ * -+ * If we encounter a component which cannot be represented as a credential -+ * value (e.g., because it contains a newline), the "gently" form will return -+ * an error but leave the broken state in the credential object for further -+ * examination. The non-gentle form will issue a warning to stderr and return -+ * an empty credential. -+ */ - void credential_from_url(struct credential *, const char *url); -+int credential_from_url_gently(struct credential *, const char *url, int quiet); -+ - int credential_match(const struct credential *have, - const struct credential *want); - -diff --git a/t/t0300-credentials.sh b/t/t0300-credentials.sh -index acc167c774..88bcf6534b 100755 ---- a/t/t0300-credentials.sh -+++ b/t/t0300-credentials.sh -@@ -289,9 +289,17 @@ test_expect_success 'http paths can be part of context' ' - EOF - ' - --test_expect_success 'url parser rejects embedded newlines' ' -- test_must_fail git credential fill <<-\EOF -+test_expect_success 'url parser ignores embedded newlines' ' -+ check fill <<-EOF - url=https://one.example.com?%0ahost=two.example.com/ -+ -- -+ username=askpass-username -+ password=askpass-password -+ -- -+ warning: url contains a newline in its host component: https://one.example.com?%0ahost=two.example.com/ -+ warning: skipping credential lookup for url: https://one.example.com?%0ahost=two.example.com/ -+ askpass: Username: -+ askpass: Password: - EOF - ' - --- -2.26.2 - - -From 3b80da45e70d60a4abc91acc45d23d18092708a4 Mon Sep 17 00:00:00 2001 -From: Jeff King -Date: Wed, 11 Mar 2020 18:48:24 -0400 -Subject: [PATCH 03/11] fsck: detect gitmodules URLs with embedded newlines - -The credential protocol can't handle values with newlines. We already -detect and block any such URLs from being used with credential helpers, -but let's also add an fsck check to detect and block gitmodules files -with such URLs. That will let us notice the problem earlier when -transfer.fsckObjects is turned on. And in particular it will prevent bad -objects from spreading, which may protect downstream users running older -versions of Git. - -We'll file this under the existing gitmodulesUrl flag, which covers URLs -with option injection. There's really no need to distinguish the exact -flaw in the URL in this context. Likewise, I've expanded the description -of t7416 to cover all types of bogus URLs. ---- - fsck.c | 16 +++++++++++++++- - t/t7416-submodule-dash-url.sh | 18 +++++++++++++++++- - 2 files changed, 32 insertions(+), 2 deletions(-) - -diff --git a/fsck.c b/fsck.c -index 1ed7172ee6..b72faea0c2 100644 ---- a/fsck.c -+++ b/fsck.c -@@ -8,6 +8,7 @@ - #include "fsck.h" - #include "hashmap.h" - #include "submodule.h" -+#include "credential.h" - - struct oidhash_entry { - struct hashmap_entry ent; -@@ -419,6 +420,19 @@ static int fsck_tag(struct tag *tag, const char *data, - return 0; - } - -+static int check_submodule_url(const char *url) -+{ -+ struct credential c = CREDENTIAL_INIT; -+ int ret; -+ -+ if (looks_like_command_line_option(url)) -+ return -1; -+ -+ ret = credential_from_url_gently(&c, url, 1); -+ credential_clear(&c); -+ return ret; -+} -+ - struct fsck_gitmodules_data { - struct object *obj; - fsck_error error_func; -@@ -442,7 +456,7 @@ static int fsck_gitmodules_fn(const char *var, const char *value, void *vdata) - "disallowed submodule name: %s", - name); - if (!strcmp(key, "url") && value && -- looks_like_command_line_option(value)) -+ check_submodule_url(value) < 0) - data->ret += data->error_func(data->obj, FSCK_ERROR, - "disallowed submodule url: %s", - value); -diff --git a/t/t7416-submodule-dash-url.sh b/t/t7416-submodule-dash-url.sh -index e85f2e9d29..5356d8766a 100755 ---- a/t/t7416-submodule-dash-url.sh -+++ b/t/t7416-submodule-dash-url.sh -@@ -1,6 +1,6 @@ - #!/bin/sh - --test_description='check handling of .gitmodule url with dash' -+test_description='check handling of disallowed .gitmodule urls' - . ./test-lib.sh - - test_expect_success 'create submodule with protected dash in url' ' -@@ -46,4 +46,20 @@ test_expect_success 'fsck rejects unprotected dash' ' - test_i18ngrep "disallowed submodule url" err - ' - -+test_expect_success 'fsck rejects embedded newline in url' ' -+ # create an orphan branch to avoid existing .gitmodules objects -+ git checkout --orphan newline && -+ cat >.gitmodules <<-\EOF && -+ [submodule "foo"] -+ url = "https://one.example.com?%0ahost=two.example.com/foo.git" -+ EOF -+ git add .gitmodules && -+ git commit -m "gitmodules with newline" && -+ test_when_finished "rm -rf dst" && -+ git init --bare dst && -+ ( cd dst && git config transfer.fsckObjects true ) && -+ test_must_fail git push dst HEAD 2>err && -+ grep "disallowed submodule url" err -+' -+ - test_done --- -2.26.2 - - -From a44bd3335d9dd715e2d141a593704aababcc83e4 Mon Sep 17 00:00:00 2001 -From: Jeff King -Date: Sat, 18 Apr 2020 20:47:30 -0700 -Subject: [PATCH 04/11] t0300: use more realistic inputs - -Many of the tests in t0300 give partial inputs to git-credential, -omitting a protocol or hostname. We're checking only high-level things -like whether and how helpers are invoked at all, and we don't care about -specific hosts. However, in preparation for tightening up the rules -about when we're willing to run a helper, let's start using input that's -a bit more realistic: pretend as if http://example.com is being -examined. - -This shouldn't change the point of any of the tests, but do note we have -to adjust the expected output to accommodate this (filling a credential -will repeat back the protocol/host fields to stdout, and the helper -debug messages and askpass prompt will change on stderr). - -Signed-off-by: Jeff King -Reviewed-by: Taylor Blau -Signed-off-by: Jonathan Nieder ---- - t/t0300-credentials.sh | 76 ++++++++++++++++++++++++++++++++++++++++-- - 1 file changed, 73 insertions(+), 3 deletions(-) - -diff --git a/t/t0300-credentials.sh b/t/t0300-credentials.sh -index 88bcf6534b..2f0ff6bcae 100755 ---- a/t/t0300-credentials.sh -+++ b/t/t0300-credentials.sh -@@ -35,43 +35,71 @@ test_expect_success 'setup helper scripts' ' - - test_expect_success 'credential_fill invokes helper' ' - check fill "verbatim foo bar" <<-\EOF -+ protocol=http -+ host=example.com - -- -+ protocol=http -+ host=example.com - username=foo - password=bar - -- - verbatim: get -+ verbatim: protocol=http -+ verbatim: host=example.com - EOF - ' - - test_expect_success 'credential_fill invokes multiple helpers' ' - check fill useless "verbatim foo bar" <<-\EOF -+ protocol=http -+ host=example.com - -- -+ protocol=http -+ host=example.com - username=foo - password=bar - -- - useless: get -+ useless: protocol=http -+ useless: host=example.com - verbatim: get -+ verbatim: protocol=http -+ verbatim: host=example.com - EOF - ' - - test_expect_success 'credential_fill stops when we get a full response' ' - check fill "verbatim one two" "verbatim three four" <<-\EOF -+ protocol=http -+ host=example.com - -- -+ protocol=http -+ host=example.com - username=one - password=two - -- - verbatim: get -+ verbatim: protocol=http -+ verbatim: host=example.com - EOF - ' - - test_expect_success 'credential_fill continues through partial response' ' - check fill "verbatim one \"\"" "verbatim two three" <<-\EOF -+ protocol=http -+ host=example.com - -- -+ protocol=http -+ host=example.com - username=two - password=three - -- - verbatim: get -+ verbatim: protocol=http -+ verbatim: host=example.com - verbatim: get -+ verbatim: protocol=http -+ verbatim: host=example.com - verbatim: username=one - EOF - ' -@@ -97,14 +125,20 @@ test_expect_success 'credential_fill passes along metadata' ' - - test_expect_success 'credential_approve calls all helpers' ' - check approve useless "verbatim one two" <<-\EOF -+ protocol=http -+ host=example.com - username=foo - password=bar - -- - -- - useless: store -+ useless: protocol=http -+ useless: host=example.com - useless: username=foo - useless: password=bar - verbatim: store -+ verbatim: protocol=http -+ verbatim: host=example.com - verbatim: username=foo - verbatim: password=bar - EOF -@@ -112,6 +146,8 @@ test_expect_success 'credential_approve calls all helpers' ' - - test_expect_success 'do not bother storing password-less credential' ' - check approve useless <<-\EOF -+ protocol=http -+ host=example.com - username=foo - -- - -- -@@ -121,14 +157,20 @@ test_expect_success 'do not bother storing password-less credential' ' - - test_expect_success 'credential_reject calls all helpers' ' - check reject useless "verbatim one two" <<-\EOF -+ protocol=http -+ host=example.com - username=foo - password=bar - -- - -- - useless: erase -+ useless: protocol=http -+ useless: host=example.com - useless: username=foo - useless: password=bar - verbatim: erase -+ verbatim: protocol=http -+ verbatim: host=example.com - verbatim: username=foo - verbatim: password=bar - EOF -@@ -136,33 +178,49 @@ test_expect_success 'credential_reject calls all helpers' ' - - test_expect_success 'usernames can be preserved' ' - check fill "verbatim \"\" three" <<-\EOF -+ protocol=http -+ host=example.com - username=one - -- -+ protocol=http -+ host=example.com - username=one - password=three - -- - verbatim: get -+ verbatim: protocol=http -+ verbatim: host=example.com - verbatim: username=one - EOF - ' - - test_expect_success 'usernames can be overridden' ' - check fill "verbatim two three" <<-\EOF -+ protocol=http -+ host=example.com - username=one - -- -+ protocol=http -+ host=example.com - username=two - password=three - -- - verbatim: get -+ verbatim: protocol=http -+ verbatim: host=example.com - verbatim: username=one - EOF - ' - - test_expect_success 'do not bother completing already-full credential' ' - check fill "verbatim three four" <<-\EOF -+ protocol=http -+ host=example.com - username=one - password=two - -- -+ protocol=http -+ host=example.com - username=one - password=two - -- -@@ -174,23 +232,31 @@ test_expect_success 'do not bother completing already-full credential' ' - # askpass helper is run, we know the internal getpass is working. - test_expect_success 'empty helper list falls back to internal getpass' ' - check fill <<-\EOF -+ protocol=http -+ host=example.com - -- -+ protocol=http -+ host=example.com - username=askpass-username - password=askpass-password - -- -- askpass: Username: -- askpass: Password: -+ askpass: Username for '\''http://example.com'\'': -+ askpass: Password for '\''http://askpass-username@example.com'\'': - EOF - ' - - test_expect_success 'internal getpass does not ask for known username' ' - check fill <<-\EOF -+ protocol=http -+ host=example.com - username=foo - -- -+ protocol=http -+ host=example.com - username=foo - password=askpass-password - -- -- askpass: Password: -+ askpass: Password for '\''http://foo@example.com'\'': - EOF - ' - -@@ -202,7 +268,11 @@ HELPER="!f() { - test_expect_success 'respect configured credentials' ' - test_config credential.helper "$HELPER" && - check fill <<-\EOF -+ protocol=http -+ host=example.com - -- -+ protocol=http -+ host=example.com - username=foo - password=bar - -- --- -2.26.2 - - -From 6eae9cd97a8f481c04dd4f8ad507eeff69a335da Mon Sep 17 00:00:00 2001 -From: Jeff King -Date: Sat, 18 Apr 2020 20:48:05 -0700 -Subject: [PATCH 05/11] credential: parse URL without host as empty host, not - unset - -We may feed a URL like "cert:///path/to/cert.pem" into the credential -machinery to get the key for a client-side certificate. That -credential has no hostname field, which is about to be disallowed (to -avoid confusion with protocols where a helper _would_ expect a -hostname). - -This means as of the next patch, credential helpers won't work for -unlocking certs. Let's fix that by doing two things: - - - when we parse a url with an empty host, set the host field to the - empty string (asking only to match stored entries with an empty - host) rather than NULL (asking to match _any_ host). - - - when we build a cert:// credential by hand, similarly assign an - empty string - -It's the latter that is more likely to impact real users in practice, -since it's what's used for http connections. But we don't have good -infrastructure to test it. - -The url-parsing version will help anybody using git-credential in a -script, and is easy to test. - -Signed-off-by: Jeff King -Reviewed-by: Taylor Blau -Signed-off-by: Jonathan Nieder ---- - credential.c | 3 +-- - http.c | 1 + - t/t0300-credentials.sh | 17 +++++++++++++++++ - 3 files changed, 19 insertions(+), 2 deletions(-) - -diff --git a/credential.c b/credential.c -index f56a0a2c02..745b0e6a6d 100644 ---- a/credential.c -+++ b/credential.c -@@ -369,8 +369,7 @@ int credential_from_url_gently(struct credential *c, const char *url, - - if (proto_end - url > 0) - c->protocol = xmemdupz(url, proto_end - url); -- if (slash - host > 0) -- c->host = url_decode_mem(host, slash - host); -+ c->host = url_decode_mem(host, slash - host); - /* Trim leading and trailing slashes from path */ - while (*slash == '/') - slash++; -diff --git a/http.c b/http.c -index 3320590e4b..1533c9338c 100644 ---- a/http.c -+++ b/http.c -@@ -269,6 +269,7 @@ static int has_cert_password(void) - return 0; - if (!cert_auth.password) { - cert_auth.protocol = xstrdup("cert"); -+ cert_auth.host = xstrdup(""); - cert_auth.username = xstrdup(""); - cert_auth.path = xstrdup(ssl_cert); - credential_fill(&cert_auth); -diff --git a/t/t0300-credentials.sh b/t/t0300-credentials.sh -index 2f0ff6bcae..0bce92af98 100755 ---- a/t/t0300-credentials.sh -+++ b/t/t0300-credentials.sh -@@ -373,4 +373,21 @@ test_expect_success 'url parser ignores embedded newlines' ' - EOF - ' - -+test_expect_success 'host-less URLs are parsed as empty host' ' -+ check fill "verbatim foo bar" <<-\EOF -+ url=cert:///path/to/cert.pem -+ -- -+ protocol=cert -+ host= -+ path=path/to/cert.pem -+ username=foo -+ password=bar -+ -- -+ verbatim: get -+ verbatim: protocol=cert -+ verbatim: host= -+ verbatim: path=path/to/cert.pem -+ EOF -+' -+ - test_done --- -2.26.2 - - -From 215df2b679d0f0d3458fafe27f4e3a3f05544400 Mon Sep 17 00:00:00 2001 -From: Jeff King -Date: Sat, 18 Apr 2020 20:50:48 -0700 -Subject: [PATCH 06/11] credential: refuse to operate when missing host or - protocol - -The credential helper protocol was designed to be very flexible: the -fields it takes as input are treated as a pattern, and any missing -fields are taken as wildcards. This allows unusual things like: - - echo protocol=https | git credential reject - -to delete all stored https credentials (assuming the helpers themselves -treat the input that way). But when helpers are invoked automatically by -Git, this flexibility works against us. If for whatever reason we don't -have a "host" field, then we'd match _any_ host. When you're filling a -credential to send to a remote server, this is almost certainly not what -you want. - -Prevent this at the layer that writes to the credential helper. Add a -check to the credential API that the host and protocol are always passed -in, and add an assertion to the credential_write function that speaks -credential helper protocol to be doubly sure. - -There are a few ways this can be triggered in practice: - - - the "git credential" command passes along arbitrary credential - parameters it reads from stdin. - - - until the previous patch, when the host field of a URL is empty, we - would leave it unset (rather than setting it to the empty string) - - - a URL like "example.com/foo.git" is treated by curl as if "http://" - was present, but our parser sees it as a non-URL and leaves all - fields unset - - - the recent fix for URLs with embedded newlines blanks the URL but - otherwise continues. Rather than having the desired effect of - looking up no credential at all, many helpers will return _any_ - credential - -Our earlier test for an embedded newline didn't catch this because it -only checked that the credential was cleared, but didn't configure an -actual helper. Configuring the "verbatim" helper in the test would show -that it is invoked (it's obviously a silly helper which doesn't look at -its input, but the point is that it shouldn't be run at all). Since -we're switching this case to die(), we don't need to bother with a -helper. We can see the new behavior just by checking that the operation -fails. - -We'll add new tests covering partial input as well (these can be -triggered through various means with url-parsing, but it's simpler to -just check them directly, as we know we are covered even if the url -parser changes behavior in the future). - -[jn: changed to die() instead of logging and showing a manual - username/password prompt] - -Reported-by: Carlo Arenas -Signed-off-by: Jeff King -Signed-off-by: Jonathan Nieder ---- - credential.c | 20 ++++++++++++++------ - t/t0300-credentials.sh | 34 ++++++++++++++++++++++++++-------- - 2 files changed, 40 insertions(+), 14 deletions(-) - -diff --git a/credential.c b/credential.c -index 745b0e6a6d..930f8b9fe9 100644 ---- a/credential.c -+++ b/credential.c -@@ -85,6 +85,11 @@ static int proto_is_http(const char *s) - - static void credential_apply_config(struct credential *c) - { -+ if (!c->host) -+ die(_("refusing to work with credential missing host field")); -+ if (!c->protocol) -+ die(_("refusing to work with credential missing protocol field")); -+ - if (c->configured) - return; - git_config(credential_config_callback, c); -@@ -186,8 +191,11 @@ int credential_read(struct credential *c, FILE *fp) - return 0; - } - --static void credential_write_item(FILE *fp, const char *key, const char *value) -+static void credential_write_item(FILE *fp, const char *key, const char *value, -+ int required) - { -+ if (!value && required) -+ die("BUG: credential value for %s is missing", key); - if (!value) - return; - if (strchr(value, '\n')) -@@ -197,11 +205,11 @@ static void credential_write_item(FILE *fp, const char *key, const char *value) - - void credential_write(const struct credential *c, FILE *fp) - { -- credential_write_item(fp, "protocol", c->protocol); -- credential_write_item(fp, "host", c->host); -- credential_write_item(fp, "path", c->path); -- credential_write_item(fp, "username", c->username); -- credential_write_item(fp, "password", c->password); -+ credential_write_item(fp, "protocol", c->protocol, 1); -+ credential_write_item(fp, "host", c->host, 1); -+ credential_write_item(fp, "path", c->path, 0); -+ credential_write_item(fp, "username", c->username, 0); -+ credential_write_item(fp, "password", c->password, 0); - } - - static int run_credential_helper(struct credential *c, -diff --git a/t/t0300-credentials.sh b/t/t0300-credentials.sh -index 0bce92af98..fa903bc9ba 100755 ---- a/t/t0300-credentials.sh -+++ b/t/t0300-credentials.sh -@@ -359,18 +359,16 @@ test_expect_success 'http paths can be part of context' ' - EOF - ' - --test_expect_success 'url parser ignores embedded newlines' ' -- check fill <<-EOF -+test_expect_success 'url parser rejects embedded newlines' ' -+ test_must_fail git credential fill 2>stderr <<-\EOF && - url=https://one.example.com?%0ahost=two.example.com/ -- -- -- username=askpass-username -- password=askpass-password -- -- -+ EOF -+ cat >expect <<-\EOF && - warning: url contains a newline in its host component: https://one.example.com?%0ahost=two.example.com/ - warning: skipping credential lookup for url: https://one.example.com?%0ahost=two.example.com/ -- askpass: Username: -- askpass: Password: -+ fatal: refusing to work with credential missing host field - EOF -+ test_i18ncmp expect stderr - ' - - test_expect_success 'host-less URLs are parsed as empty host' ' -@@ -390,4 +388,24 @@ test_expect_success 'host-less URLs are parsed as empty host' ' - EOF - ' - -+test_expect_success 'credential system refuses to work with missing host' ' -+ test_must_fail git credential fill 2>stderr <<-\EOF && -+ protocol=http -+ EOF -+ cat >expect <<-\EOF && -+ fatal: refusing to work with credential missing host field -+ EOF -+ test_i18ncmp expect stderr -+' -+ -+test_expect_success 'credential system refuses to work with missing protocol' ' -+ test_must_fail git credential fill 2>stderr <<-\EOF && -+ host=example.com -+ EOF -+ cat >expect <<-\EOF && -+ fatal: refusing to work with credential missing protocol field -+ EOF -+ test_i18ncmp expect stderr -+' -+ - test_done --- -2.26.2 - - -From 38cf97b5bb0fe73ae21f9a840caeb26c600cb624 Mon Sep 17 00:00:00 2001 -From: Jonathan Nieder -Date: Sat, 18 Apr 2020 20:52:34 -0700 -Subject: [PATCH 07/11] fsck: convert gitmodules url to URL passed to curl - -In 07259e74ec1 (fsck: detect gitmodules URLs with embedded newlines, -2020-03-11), git fsck learned to check whether URLs in .gitmodules could -be understood by the credential machinery when they are handled by -git-remote-curl. - -However, the check is overbroad: it checks all URLs instead of only -URLs that would be passed to git-remote-curl. In principle a git:// or -file:/// URL does not need to follow the same conventions as an http:// -URL; in particular, git:// and file:// protocols are not succeptible to -issues in the credential API because they do not support attaching -credentials. - -In the HTTP case, the URL in .gitmodules does not always match the URL -that would be passed to git-remote-curl and the credential machinery: -Git's URL syntax allows specifying a remote helper followed by a "::" -delimiter and a URL to be passed to it, so that - - git ls-remote http::https://example.com/repo.git - -invokes git-remote-http with https://example.com/repo.git as its URL -argument. With today's checks, that distinction does not make a -difference, but for a check we are about to introduce (for empty URL -schemes) it will matter. - -.gitmodules files also support relative URLs. To ensure coverage for the -https based embedded-newline attack, urldecode and check them directly -for embedded newlines. - -Helped-by: Jeff King -Signed-off-by: Jonathan Nieder -Reviewed-by: Jeff King ---- - fsck.c | 94 +++++++++++++++++++++++++++++++++-- - t/t7416-submodule-dash-url.sh | 29 +++++++++++ - 2 files changed, 118 insertions(+), 5 deletions(-) - -diff --git a/fsck.c b/fsck.c -index b72faea0c2..a265568eda 100644 ---- a/fsck.c -+++ b/fsck.c -@@ -8,6 +8,7 @@ - #include "fsck.h" - #include "hashmap.h" - #include "submodule.h" -+#include "url.h" - #include "credential.h" - - struct oidhash_entry { -@@ -420,17 +421,100 @@ static int fsck_tag(struct tag *tag, const char *data, - return 0; - } - -+/* -+ * Like builtin/submodule--helper.c's starts_with_dot_slash, but without -+ * relying on the platform-dependent is_dir_sep helper. -+ * -+ * This is for use in checking whether a submodule URL is interpreted as -+ * relative to the current directory on any platform, since \ is a -+ * directory separator on Windows but not on other platforms. -+ */ -+static int starts_with_dot_slash(const char *str) -+{ -+ return str[0] == '.' && (str[1] == '/' || str[1] == '\\'); -+} -+ -+/* -+ * Like starts_with_dot_slash, this is a variant of submodule--helper's -+ * helper of the same name with the twist that it accepts backslash as a -+ * directory separator even on non-Windows platforms. -+ */ -+static int starts_with_dot_dot_slash(const char *str) -+{ -+ return str[0] == '.' && starts_with_dot_slash(str + 1); -+} -+ -+static int submodule_url_is_relative(const char *url) -+{ -+ return starts_with_dot_slash(url) || starts_with_dot_dot_slash(url); -+} -+ -+/* -+ * Check whether a transport is implemented by git-remote-curl. -+ * -+ * If it is, returns 1 and writes the URL that would be passed to -+ * git-remote-curl to the "out" parameter. -+ * -+ * Otherwise, returns 0 and leaves "out" untouched. -+ * -+ * Examples: -+ * http::https://example.com/repo.git -> 1, https://example.com/repo.git -+ * https://example.com/repo.git -> 1, https://example.com/repo.git -+ * git://example.com/repo.git -> 0 -+ * -+ * This is for use in checking for previously exploitable bugs that -+ * required a submodule URL to be passed to git-remote-curl. -+ */ -+static int url_to_curl_url(const char *url, const char **out) -+{ -+ /* -+ * We don't need to check for case-aliases, "http.exe", and so -+ * on because in the default configuration, is_transport_allowed -+ * prevents URLs with those schemes from being cloned -+ * automatically. -+ */ -+ if ((*out = skip_prefix(url, "http::")) || -+ (*out = skip_prefix(url, "https::")) || -+ (*out = skip_prefix(url, "ftp::")) || -+ (*out = skip_prefix(url, "ftps::"))) -+ return 1; -+ if (starts_with(url, "http://") || -+ starts_with(url, "https://") || -+ starts_with(url, "ftp://") || -+ starts_with(url, "ftps://")) { -+ *out = url; -+ return 1; -+ } -+ return 0; -+} -+ - static int check_submodule_url(const char *url) - { -- struct credential c = CREDENTIAL_INIT; -- int ret; -+ const char *curl_url; - - if (looks_like_command_line_option(url)) - return -1; - -- ret = credential_from_url_gently(&c, url, 1); -- credential_clear(&c); -- return ret; -+ if (submodule_url_is_relative(url)) { -+ /* -+ * This could be appended to an http URL and url-decoded; -+ * check for malicious characters. -+ */ -+ char *decoded = url_decode(url); -+ int has_nl = !!strchr(decoded, '\n'); -+ free(decoded); -+ if (has_nl) -+ return -1; -+ } -+ -+ else if (url_to_curl_url(url, &curl_url)) { -+ struct credential c = CREDENTIAL_INIT; -+ int ret = credential_from_url_gently(&c, curl_url, 1); -+ credential_clear(&c); -+ return ret; -+ } -+ -+ return 0; - } - - struct fsck_gitmodules_data { -diff --git a/t/t7416-submodule-dash-url.sh b/t/t7416-submodule-dash-url.sh -index 5356d8766a..41bec56953 100755 ---- a/t/t7416-submodule-dash-url.sh -+++ b/t/t7416-submodule-dash-url.sh -@@ -46,6 +46,20 @@ test_expect_success 'fsck rejects unprotected dash' ' - test_i18ngrep "disallowed submodule url" err - ' - -+test_expect_success 'fsck permits embedded newline with unrecognized scheme' ' -+ git checkout --orphan newscheme && -+ cat >.gitmodules <<-\EOF && -+ [submodule "foo"] -+ url = "data://acjbkd%0akajfdickajkd" -+ EOF -+ git add .gitmodules && -+ git commit -m "gitmodules with unrecognized scheme" && -+ test_when_finished "rm -rf dst" && -+ git init --bare dst && -+ ( cd dst && git config transfer.fsckObjects true ) && -+ git push dst HEAD -+' -+ - test_expect_success 'fsck rejects embedded newline in url' ' - # create an orphan branch to avoid existing .gitmodules objects - git checkout --orphan newline && -@@ -62,4 +76,19 @@ test_expect_success 'fsck rejects embedded newline in url' ' - grep "disallowed submodule url" err - ' - -+test_expect_success 'fsck rejects embedded newline in relative url' ' -+ git checkout --orphan relative-newline && -+ cat >.gitmodules <<-\EOF && -+ [submodule "foo"] -+ url = "./%0ahost=two.example.com/foo.git" -+ EOF -+ git add .gitmodules && -+ git commit -m "relative url with newline" && -+ test_when_finished "rm -rf dst" && -+ git init --bare dst && -+ ( cd dst && git config transfer.fsckObjects true ) && -+ test_must_fail git push dst HEAD 2>err && -+ grep "disallowed submodule url" err -+' -+ - test_done --- -2.26.2 - - -From d98d29c00f88d6fe399dd11fd77b88074bd49924 Mon Sep 17 00:00:00 2001 -From: Jeff King -Date: Sat, 18 Apr 2020 20:53:09 -0700 -Subject: [PATCH 08/11] credential: die() when parsing invalid urls - -When we try to initialize credential loading by URL and find that the -URL is invalid, we set all fields to NULL in order to avoid acting on -malicious input. Later when we request credentials, we diagonse the -erroneous input: - - fatal: refusing to work with credential missing host field - -This is problematic in two ways: - -- The message doesn't tell the user *why* we are missing the host - field, so they can't tell from this message alone how to recover. - There can be intervening messages after the original warning of - bad input, so the user may not have the context to put two and two - together. - -- The error only occurs when we actually need to get a credential. If - the URL permits anonymous access, the only encouragement the user gets - to correct their bogus URL is a quiet warning. - - This is inconsistent with the check we perform in fsck, where any use - of such a URL as a submodule is an error. - -When we see such a bogus URL, let's not try to be nice and continue -without helpers. Instead, die() immediately. This is simpler and -obviously safe. And there's very little chance of disrupting a normal -workflow. - -It's _possible_ that somebody has a legitimate URL with a raw newline in -it. It already wouldn't work with credential helpers, so this patch -steps that up from an inconvenience to "we will refuse to work with it -at all". If such a case does exist, we should figure out a way to work -with it (especially if the newline is only in the path component, which -we normally don't even pass to helpers). But until we see a real report, -we're better off being defensive. - -Reported-by: Carlo Arenas -Signed-off-by: Jeff King -Signed-off-by: Jonathan Nieder ---- - credential.c | 6 ++---- - t/t0300-credentials.sh | 3 +-- - 2 files changed, 3 insertions(+), 6 deletions(-) - -diff --git a/credential.c b/credential.c -index 930f8b9fe9..725e917b2d 100644 ---- a/credential.c -+++ b/credential.c -@@ -401,8 +401,6 @@ int credential_from_url_gently(struct credential *c, const char *url, - - void credential_from_url(struct credential *c, const char *url) - { -- if (credential_from_url_gently(c, url, 0) < 0) { -- warning(_("skipping credential lookup for url: %s"), url); -- credential_clear(c); -- } -+ if (credential_from_url_gently(c, url, 0) < 0) -+ die(_("credential url cannot be parsed: %s"), url); - } -diff --git a/t/t0300-credentials.sh b/t/t0300-credentials.sh -index fa903bc9ba..9892464eba 100755 ---- a/t/t0300-credentials.sh -+++ b/t/t0300-credentials.sh -@@ -365,8 +365,7 @@ test_expect_success 'url parser rejects embedded newlines' ' - EOF - cat >expect <<-\EOF && - warning: url contains a newline in its host component: https://one.example.com?%0ahost=two.example.com/ -- warning: skipping credential lookup for url: https://one.example.com?%0ahost=two.example.com/ -- fatal: refusing to work with credential missing host field -+ fatal: credential url cannot be parsed: https://one.example.com?%0ahost=two.example.com/ - EOF - test_i18ncmp expect stderr - ' --- -2.26.2 - - -From accb9edcd64007cf940be7b9e6a209ebcc650e8c Mon Sep 17 00:00:00 2001 -From: Jonathan Nieder -Date: Sat, 18 Apr 2020 20:54:13 -0700 -Subject: [PATCH 09/11] credential: treat URL without scheme as invalid - -libcurl permits making requests without a URL scheme specified. In -this case, it guesses the URL from the hostname, so I can run - - git ls-remote http::ftp.example.com/path/to/repo - -and it would make an FTP request. - -Any user intentionally using such a URL is likely to have made a typo. -Unfortunately, credential_from_url is not able to determine the host and -protocol in order to determine appropriate credentials to send, and -until "credential: refuse to operate when missing host or protocol", -this resulted in another host's credentials being leaked to the named -host. - -Teach credential_from_url_gently to consider such a URL to be invalid -so that fsck can detect and block gitmodules files with such URLs, -allowing server operators to avoid serving them to downstream users -running older versions of Git. - -This also means that when such URLs are passed on the command line, Git -will print a clearer error so affected users can switch to the simpler -URL that explicitly specifies the host and protocol they intend. - -One subtlety: .gitmodules files can contain relative URLs, representing -a URL relative to the URL they were cloned from. The relative URL -resolver used for .gitmodules can follow ".." components out of the path -part and past the host part of a URL, meaning that such a relative URL -can be used to traverse from a https://foo.example.com/innocent -superproject to a https::attacker.example.com/exploit submodule. -Fortunately a leading ':' in the first path component after a series of -leading './' and '../' components is unlikely to show up in other -contexts, so we can catch this by detecting that pattern. - -Reported-by: Jeff King -Signed-off-by: Jonathan Nieder -Reviewed-by: Jeff King ---- - credential.c | 7 ++++-- - fsck.c | 47 +++++++++++++++++++++++++++++++++-- - t/t5550-http-fetch.sh | 5 ++++ - t/t7416-submodule-dash-url.sh | 32 ++++++++++++++++++++++++ - 4 files changed, 87 insertions(+), 4 deletions(-) - -diff --git a/credential.c b/credential.c -index 725e917b2d..f857f7d1e1 100644 ---- a/credential.c -+++ b/credential.c -@@ -353,8 +353,11 @@ int credential_from_url_gently(struct credential *c, const char *url, - * (3) proto://:@/... - */ - proto_end = strstr(url, "://"); -- if (!proto_end) -- return 0; -+ if (!proto_end) { -+ if (!quiet) -+ warning(_("url has no scheme: %s"), url); -+ return -1; -+ } - cp = proto_end + 3; - at = strchr(cp, '@'); - colon = strchr(cp, ':'); -diff --git a/fsck.c b/fsck.c -index a265568eda..90aea117c9 100644 ---- a/fsck.c -+++ b/fsck.c -@@ -449,6 +449,34 @@ static int submodule_url_is_relative(const char *url) - return starts_with_dot_slash(url) || starts_with_dot_dot_slash(url); - } - -+/* -+ * Count directory components that a relative submodule URL should chop -+ * from the remote_url it is to be resolved against. -+ * -+ * In other words, this counts "../" components at the start of a -+ * submodule URL. -+ * -+ * Returns the number of directory components to chop and writes a -+ * pointer to the next character of url after all leading "./" and -+ * "../" components to out. -+ */ -+static int count_leading_dotdots(const char *url, const char **out) -+{ -+ int result = 0; -+ while (1) { -+ if (starts_with_dot_dot_slash(url)) { -+ result++; -+ url += strlen("../"); -+ continue; -+ } -+ if (starts_with_dot_slash(url)) { -+ url += strlen("./"); -+ continue; -+ } -+ *out = url; -+ return result; -+ } -+} - /* - * Check whether a transport is implemented by git-remote-curl. - * -@@ -496,15 +524,30 @@ static int check_submodule_url(const char *url) - return -1; - - if (submodule_url_is_relative(url)) { -+ char *decoded; -+ const char *next; -+ int has_nl; -+ - /* - * This could be appended to an http URL and url-decoded; - * check for malicious characters. - */ -- char *decoded = url_decode(url); -- int has_nl = !!strchr(decoded, '\n'); -+ decoded = url_decode(url); -+ has_nl = !!strchr(decoded, '\n'); -+ - free(decoded); - if (has_nl) - return -1; -+ -+ /* -+ * URLs which escape their root via "../" can overwrite -+ * the host field and previous components, resolving to -+ * URLs like https::example.com/submodule.git that were -+ * susceptible to CVE-2020-11008. -+ */ -+ if (count_leading_dotdots(url, &next) > 0 && -+ *next == ':') -+ return -1; - } - - else if (url_to_curl_url(url, &curl_url)) { -diff --git a/t/t5550-http-fetch.sh b/t/t5550-http-fetch.sh -index f7d0f146f0..29fdae9a68 100755 ---- a/t/t5550-http-fetch.sh -+++ b/t/t5550-http-fetch.sh -@@ -172,5 +172,10 @@ test_expect_success 'did not use upload-pack service' ' - test_cmp exp act - ' - -+test_expect_success 'remote-http complains cleanly about malformed urls' ' -+ test_must_fail git remote-http http::/example.com/repo.git 2>stderr && -+ test_i18ngrep "url has no scheme" stderr -+' -+ - stop_httpd - test_done -diff --git a/t/t7416-submodule-dash-url.sh b/t/t7416-submodule-dash-url.sh -index 41bec56953..1dc8333eb3 100755 ---- a/t/t7416-submodule-dash-url.sh -+++ b/t/t7416-submodule-dash-url.sh -@@ -46,6 +46,38 @@ test_expect_success 'fsck rejects unprotected dash' ' - test_i18ngrep "disallowed submodule url" err - ' - -+test_expect_success 'fsck rejects missing URL scheme' ' -+ git checkout --orphan missing-scheme && -+ cat >.gitmodules <<-\EOF && -+ [submodule "foo"] -+ url = http::one.example.com/foo.git -+ EOF -+ git add .gitmodules && -+ test_tick && -+ git commit -m "gitmodules with missing URL scheme" && -+ test_when_finished "rm -rf dst" && -+ git init --bare dst && -+ ( cd dst && git config transfer.fsckObjects true ) && -+ test_must_fail git push dst HEAD 2>err && -+ grep "disallowed submodule url" err -+' -+ -+test_expect_success 'fsck rejects relative URL resolving to missing scheme' ' -+ git checkout --orphan relative-missing-scheme && -+ cat >.gitmodules <<-\EOF && -+ [submodule "foo"] -+ url = "..\\../.\\../:one.example.com/foo.git" -+ EOF -+ git add .gitmodules && -+ test_tick && -+ git commit -m "gitmodules with relative URL that strips off scheme" && -+ test_when_finished "rm -rf dst" && -+ git init --bare dst && -+ ( cd dst && git config transfer.fsckObjects true ) && -+ test_must_fail git push dst HEAD 2>err && -+ grep "disallowed submodule url" err -+' -+ - test_expect_success 'fsck permits embedded newline with unrecognized scheme' ' - git checkout --orphan newscheme && - cat >.gitmodules <<-\EOF && --- -2.26.2 - - -From 8bd36d7015bc9ffbd8ef58227ae5d9928fa708cb Mon Sep 17 00:00:00 2001 -From: Jonathan Nieder -Date: Sat, 18 Apr 2020 20:54:57 -0700 -Subject: [PATCH 10/11] credential: treat URL with empty scheme as invalid - -Until "credential: refuse to operate when missing host or protocol", -Git's credential handling code interpreted URLs with empty scheme to -mean "give me credentials matching this host for any protocol". - -Luckily libcurl does not recognize such URLs (it tries to look for a -protocol named "" and fails). Just in case that changes, let's reject -them within Git as well. This way, credential_from_url is guaranteed to -always produce a "struct credential" with protocol and host set. - -Signed-off-by: Jonathan Nieder ---- - credential.c | 5 ++--- - t/t5550-http-fetch.sh | 9 +++++++++ - t/t7416-submodule-dash-url.sh | 32 ++++++++++++++++++++++++++++++++ - 3 files changed, 43 insertions(+), 3 deletions(-) - -diff --git a/credential.c b/credential.c -index f857f7d1e1..323f9025f0 100644 ---- a/credential.c -+++ b/credential.c -@@ -353,7 +353,7 @@ int credential_from_url_gently(struct credential *c, const char *url, - * (3) proto://:@/... - */ - proto_end = strstr(url, "://"); -- if (!proto_end) { -+ if (!proto_end || proto_end == url) { - if (!quiet) - warning(_("url has no scheme: %s"), url); - return -1; -@@ -378,8 +378,7 @@ int credential_from_url_gently(struct credential *c, const char *url, - host = at + 1; - } - -- if (proto_end - url > 0) -- c->protocol = xmemdupz(url, proto_end - url); -+ c->protocol = xmemdupz(url, proto_end - url); - c->host = url_decode_mem(host, slash - host); - /* Trim leading and trailing slashes from path */ - while (*slash == '/') -diff --git a/t/t5550-http-fetch.sh b/t/t5550-http-fetch.sh -index 29fdae9a68..afeb90fa66 100755 ---- a/t/t5550-http-fetch.sh -+++ b/t/t5550-http-fetch.sh -@@ -177,5 +177,14 @@ test_expect_success 'remote-http complains cleanly about malformed urls' ' - test_i18ngrep "url has no scheme" stderr - ' - -+# NEEDSWORK: Writing commands to git-remote-curl can race against the latter -+# erroring out, producing SIGPIPE. Remove "ok=sigpipe" once transport-helper has -+# learned to handle early remote helper failures more cleanly. -+test_expect_success 'remote-http complains cleanly about empty scheme' ' -+ test_must_fail ok=sigpipe git ls-remote \ -+ http::${HTTPD_URL#http}/dumb/repo.git 2>stderr && -+ test_i18ngrep "url has no scheme" stderr -+' -+ - stop_httpd - test_done -diff --git a/t/t7416-submodule-dash-url.sh b/t/t7416-submodule-dash-url.sh -index 1dc8333eb3..9c7a8113fa 100755 ---- a/t/t7416-submodule-dash-url.sh -+++ b/t/t7416-submodule-dash-url.sh -@@ -78,6 +78,38 @@ test_expect_success 'fsck rejects relative URL resolving to missing scheme' ' - grep "disallowed submodule url" err - ' - -+test_expect_success 'fsck rejects empty URL scheme' ' -+ git checkout --orphan empty-scheme && -+ cat >.gitmodules <<-\EOF && -+ [submodule "foo"] -+ url = http::://one.example.com/foo.git -+ EOF -+ git add .gitmodules && -+ test_tick && -+ git commit -m "gitmodules with empty URL scheme" && -+ test_when_finished "rm -rf dst" && -+ git init --bare dst && -+ ( cd dst && git config transfer.fsckObjects true ) && -+ test_must_fail git push dst HEAD 2>err && -+ grep "disallowed submodule url" err -+' -+ -+test_expect_success 'fsck rejects relative URL resolving to empty scheme' ' -+ git checkout --orphan relative-empty-scheme && -+ cat >.gitmodules <<-\EOF && -+ [submodule "foo"] -+ url = ../../../:://one.example.com/foo.git -+ EOF -+ git add .gitmodules && -+ test_tick && -+ git commit -m "relative gitmodules URL resolving to empty scheme" && -+ test_when_finished "rm -rf dst" && -+ git init --bare dst && -+ ( cd dst && git config transfer.fsckObjects true ) && -+ test_must_fail git push dst HEAD 2>err && -+ grep "disallowed submodule url" err -+' -+ - test_expect_success 'fsck permits embedded newline with unrecognized scheme' ' - git checkout --orphan newscheme && - cat >.gitmodules <<-\EOF && --- -2.26.2 - - -From e9d4a9cf1445cff676b0147578a62be6a916f675 Mon Sep 17 00:00:00 2001 -From: Jonathan Nieder -Date: Sat, 18 Apr 2020 20:57:22 -0700 -Subject: [PATCH 11/11] fsck: reject URL with empty host in .gitmodules - -Git's URL parser interprets - - https:///example.com/repo.git - -to have no host and a path of "example.com/repo.git". Curl, on the -other hand, internally redirects it to https://example.com/repo.git. As -a result, until "credential: parse URL without host as empty host, not -unset", tricking a user into fetching from such a URL would cause Git to -send credentials for another host to example.com. - -Teach fsck to block and detect .gitmodules files using such a URL to -prevent sharing them with Git versions that are not yet protected. - -A relative URL in a .gitmodules file could also be used to trigger this. -The relative URL resolver used for .gitmodules does not normalize -sequences of slashes and can follow ".." components out of the path part -and to the host part of a URL, meaning that such a relative URL can be -used to traverse from a https://foo.example.com/innocent superproject to -a https:///attacker.example.com/exploit submodule. Fortunately, -redundant extra slashes in .gitmodules are rare, so we can catch this by -detecting one after a leading sequence of "./" and "../" components. - -Helped-by: Jeff King -Signed-off-by: Jonathan Nieder -Reviewed-by: Jeff King ---- - fsck.c | 10 +++++++--- - t/t7416-submodule-dash-url.sh | 32 ++++++++++++++++++++++++++++++++ - 2 files changed, 39 insertions(+), 3 deletions(-) - -diff --git a/fsck.c b/fsck.c -index 90aea117c9..b5e0a79cda 100644 ---- a/fsck.c -+++ b/fsck.c -@@ -542,17 +542,21 @@ static int check_submodule_url(const char *url) - /* - * URLs which escape their root via "../" can overwrite - * the host field and previous components, resolving to -- * URLs like https::example.com/submodule.git that were -+ * URLs like https::example.com/submodule.git and -+ * https:///example.com/submodule.git that were - * susceptible to CVE-2020-11008. - */ - if (count_leading_dotdots(url, &next) > 0 && -- *next == ':') -+ (*next == ':' || *next == '/')) - return -1; - } - - else if (url_to_curl_url(url, &curl_url)) { - struct credential c = CREDENTIAL_INIT; -- int ret = credential_from_url_gently(&c, curl_url, 1); -+ int ret = 0; -+ if (credential_from_url_gently(&c, curl_url, 1) || -+ !*c.host) -+ ret = -1; - credential_clear(&c); - return ret; - } -diff --git a/t/t7416-submodule-dash-url.sh b/t/t7416-submodule-dash-url.sh -index 9c7a8113fa..e65238f3c5 100755 ---- a/t/t7416-submodule-dash-url.sh -+++ b/t/t7416-submodule-dash-url.sh -@@ -110,6 +110,38 @@ test_expect_success 'fsck rejects relative URL resolving to empty scheme' ' - grep "disallowed submodule url" err - ' - -+test_expect_success 'fsck rejects empty hostname' ' -+ git checkout --orphan empty-host && -+ cat >.gitmodules <<-\EOF && -+ [submodule "foo"] -+ url = http:///one.example.com/foo.git -+ EOF -+ git add .gitmodules && -+ test_tick && -+ git commit -m "gitmodules with extra slashes" && -+ test_when_finished "rm -rf dst" && -+ git init --bare dst && -+ ( cd dst && git config transfer.fsckObjects true ) && -+ test_must_fail git push dst HEAD 2>err && -+ grep "disallowed submodule url" err -+' -+ -+test_expect_success 'fsck rejects relative url that produced empty hostname' ' -+ git checkout --orphan messy-relative && -+ cat >.gitmodules <<-\EOF && -+ [submodule "foo"] -+ url = ../../..//one.example.com/foo.git -+ EOF -+ git add .gitmodules && -+ test_tick && -+ git commit -m "gitmodules abusing relative_path" && -+ test_when_finished "rm -rf dst" && -+ git init --bare dst && -+ ( cd dst && git config transfer.fsckObjects true ) && -+ test_must_fail git push dst HEAD 2>err && -+ grep "disallowed submodule url" err -+' -+ - test_expect_success 'fsck permits embedded newline with unrecognized scheme' ' - git checkout --orphan newscheme && - cat >.gitmodules <<-\EOF && --- -2.26.2 - diff --git a/git-2.43.0.tar.sign b/git-2.43.0.tar.sign new file mode 100644 index 0000000000000000000000000000000000000000..d72606da6f8e997bd468ecd6bb6b3faee9549f00 Binary files /dev/null and b/git-2.43.0.tar.sign differ diff --git a/git-2.43.0.tar.xz b/git-2.43.0.tar.xz new file mode 100644 index 0000000000000000000000000000000000000000..2a0c1d304e9be230c636685612e069b6f8bcb7ca Binary files /dev/null and b/git-2.43.0.tar.xz differ diff --git a/git-cve-2017-1000117.patch b/git-cve-2017-1000117.patch deleted file mode 100644 index ccdfa9c2b5734a9df323e7264e26498cacabce13..0000000000000000000000000000000000000000 --- a/git-cve-2017-1000117.patch +++ /dev/null @@ -1,85 +0,0 @@ -diff --git a/cache.h b/cache.h -index 94ca1ac..2ab9ffd 100644 ---- a/cache.h -+++ b/cache.h -@@ -744,6 +744,14 @@ char *strip_path_suffix(const char *path, const char *suffix); - int daemon_avoid_alias(const char *path); - int offset_1st_component(const char *path); - -+/* -+ * 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); -+ - /* object replacement */ - #define READ_SHA1_FILE_REPLACE 1 - extern void *read_sha1_file_extended(const unsigned char *sha1, enum object_type *type, unsigned long *size, unsigned flag); -diff --git a/connect.c b/connect.c -index 6d4ea13..970f565 100644 ---- a/connect.c -+++ b/connect.c -@@ -450,6 +450,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); -+ - argv = xmalloc(sizeof(*argv) * 4); - argv[0] = git_proxy_command; - argv[1] = host; -@@ -613,6 +618,10 @@ struct child_process *git_connect(int fd[2], const char *url_orig, - - conn = xcalloc(1, sizeof(*conn)); - -+ if (looks_like_command_line_option(path)) -+ die("strange pathname '%s' blocked", path); -+ -+ - strbuf_init(&cmd, MAX_CMD_LEN); - strbuf_addstr(&cmd, prog); - strbuf_addch(&cmd, ' '); -@@ -626,6 +635,10 @@ struct child_process *git_connect(int fd[2], const char *url_orig, - const char *ssh = getenv("GIT_SSH"); - int putty = ssh && strcasestr(ssh, "plink"); - transport_check_allowed("ssh"); -+ if (looks_like_command_line_option(host)) -+ die("strange hostname '%s' blocked", host); -+ -+ - if (!ssh) ssh = "ssh"; - - *arg++ = ssh; -diff --git a/path.c b/path.c -index 04ff148..713d79b 100644 ---- a/path.c -+++ b/path.c -@@ -701,3 +701,9 @@ int offset_1st_component(const char *path) - return 2 + is_dir_sep(path[2]); - return is_dir_sep(path[0]); - } -+ -+int looks_like_command_line_option(const char *str) -+{ -+ return str && str[0] == '-'; -+} -+ -diff --git a/t/t5532-fetch-proxy.sh b/t/t5532-fetch-proxy.sh -index 5531bd1..d3b2651 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 diff --git a/git-cve-2018-11235.patch b/git-cve-2018-11235.patch deleted file mode 100644 index 7b783d744507ba80bfc7241a6a99b32521ac9e41..0000000000000000000000000000000000000000 --- a/git-cve-2018-11235.patch +++ /dev/null @@ -1,5002 +0,0 @@ -From 1e9bfbbfca9a4003368352d173815de134f9bcd9 Mon Sep 17 00:00:00 2001 -From: Pavel Cahyna -Date: Mon, 18 Jun 2018 13:58:25 +0200 -Subject: [PATCH] Squashed commit of the following: - -commit a1e311b306db9407e0bf83046dce50ef6a7f74bb -Author: Jeff King -Date: Mon Jan 16 16:24:03 2017 -0500 - - t1450: clean up sub-objects in duplicate-entry test - - This test creates a multi-level set of trees, but its - cleanup routine only removes the top-level tree. After the - test finishes, the inner tree and the blob it points to - remain, making the inner tree dangling. - - A later test ("cleaned up") verifies that we've removed any - cruft and "git fsck" output is clean. This passes only - because of a bug in git-fsck which fails to notice dangling - trees. - - In preparation for fixing the bug, let's teach this earlier - test to clean up after itself correctly. We have to remove - the inner tree (and therefore the blob, too, which becomes - dangling after removing that tree). - - Since the setup code happens inside a subshell, we can't - just set a variable for each object. However, we can stuff - all of the sha1s into the $T output variable, which is not - used for anything except cleanup. - - Signed-off-by: Jeff King - Signed-off-by: Junio C Hamano - -commit e71a6f0c8a80829017629d1ae595f6a887c4e844 -Author: Pavel Cahyna -Date: Fri Jun 15 11:49:09 2018 +0200 - - Adapt t7415-submodule-names.sh to git 1.8.3.1 : we don't have -C - -commit ba4d4fca832bf7c2ec224ada5243e950d8e32406 -Author: Jeff King -Date: Fri May 4 20:03:35 2018 -0400 - - fsck: complain when .gitmodules is a symlink - - commit b7b1fca175f1ed7933f361028c631b9ac86d868d upstream. - - We've recently forbidden .gitmodules to be a symlink in - verify_path(). And it's an easy way to circumvent our fsck - checks for .gitmodules content. So let's complain when we - see it. - - [jn: backported to 2.1.y: - - using error_func instead of report to report fsck errors - - until v2.6.2~7^2 (fsck: exit with non-zero when problems - are found, 2015-09-23), git fsck did not reliably use the - exit status to indicate errors; callers would have to - check stderr instead. Relaxed the test to permit that. - [pc: revert the above and fix the actual error by moving the - initialization of retval earlier so it is not clobbered]] - - Signed-off-by: Jeff King - Signed-off-by: Jonathan Nieder - -commit d55fa465ac0c2d825f80e8ad0cbbca877812c0b1 -Author: Jeff King -Date: Fri May 4 19:45:01 2018 -0400 - - index-pack: check .gitmodules files with --strict - - commit 73c3f0f704a91b6792e0199a3f3ab6e3a1971675 upstream. - - Now that the internal fsck code has all of the plumbing we - need, we can start checking incoming .gitmodules files. - Naively, it seems like we would just need to add a call to - fsck_finish() after we've processed all of the objects. And - that would be enough to cover the initial test included - here. But there are two extra bits: - - 1. We currently don't bother calling fsck_object() at all - for blobs, since it has traditionally been a noop. We'd - actually catch these blobs in fsck_finish() at the end, - but it's more efficient to check them when we already - have the object loaded in memory. - - 2. The second pass done by fsck_finish() needs to access - the objects, but we're actually indexing the pack in - this process. In theory we could give the fsck code a - special callback for accessing the in-pack data, but - it's actually quite tricky: - - a. We don't have an internal efficient index mapping - oids to packfile offsets. We only generate it on - the fly as part of writing out the .idx file. - - b. We'd still have to reconstruct deltas, which means - we'd basically have to replicate all of the - reading logic in packfile.c. - - Instead, let's avoid running fsck_finish() until after - we've written out the .idx file, and then just add it - to our internal packed_git list. - - This does mean that the objects are "in the repository" - before we finish our fsck checks. But unpack-objects - already exhibits this same behavior, and it's an - acceptable tradeoff here for the same reason: the - quarantine mechanism means that pushes will be - fully protected. - - In addition to a basic push test in t7415, we add a sneaky - pack that reverses the usual object order in the pack, - requiring that index-pack access the tree and blob during - the "finish" step. - - This already works for unpack-objects (since it will have - written out loose objects), but we'll check it with this - sneaky pack for good measure. - - Signed-off-by: Jeff King - Signed-off-by: Jonathan Nieder - -commit 098925bcbeaa8aada335cdd48135efa83e3710d8 -Author: Jeff King -Date: Fri May 4 19:40:08 2018 -0400 - - unpack-objects: call fsck_finish() after fscking objects - - commit 6e328d6caef218db320978e3e251009135d87d0e upstream. - - As with the previous commit, we must call fsck's "finish" - function in order to catch any queued objects for - .gitmodules checks. - - This second pass will be able to access any incoming - objects, because we will have exploded them to loose objects - by now. - - This isn't quite ideal, because it means that bad objects - may have been written to the object database (and a - subsequent operation could then reference them, even if the - other side doesn't send the objects again). However, this is - sufficient when used with receive.fsckObjects, since those - loose objects will all be placed in a temporary quarantine - area that will get wiped if we find any problems. - - Signed-off-by: Jeff King - Signed-off-by: Jonathan Nieder - -commit 7f1b69637f1744e42e61fd18c7ac4dac75edd6fb -Author: Jeff King -Date: Wed May 2 17:20:35 2018 -0400 - - fsck: call fsck_finish() after fscking objects - - commit 1995b5e03e1cc97116be58cdc0502d4a23547856 upstream. - - Now that the internal fsck code is capable of checking - .gitmodules files, we just need to teach its callers to use - the "finish" function to check any queued objects. - - With this, we can now catch the malicious case in t7415 with - git-fsck. - - Signed-off-by: Jeff King - Signed-off-by: Jonathan Nieder - -commit e197e085e86c5ebb2eb6f4556f6e82e3bdf8afa6 -Author: Jeff King -Date: Wed May 2 17:25:27 2018 -0400 - - fsck: check .gitmodules content - - commit ed8b10f631c9a71df3351d46187bf7f3fa4f9b7e upstream. - - This patch detects and blocks submodule names which do not - match the policy set forth in submodule-config. These should - already be caught by the submodule code itself, but putting - the check here means that newer versions of Git can protect - older ones from malicious entries (e.g., a server with - receive.fsckObjects will block the objects, protecting - clients which fetch from it). - - As a side effect, this means fsck will also complain about - .gitmodules files that cannot be parsed (or were larger than - core.bigFileThreshold). - - [jn: backported by using git_config_from_buf instead of - git_config_from_mem for parsing and error_func instead of - report for reporting fsck errors] - - Signed-off-by: Jeff King - Signed-off-by: Jonathan Nieder - -commit ad298dfb0e89d05db3f17d9a0997f065c7ed96f5 -Author: Jeff King -Date: Wed May 2 17:20:08 2018 -0400 - - fsck: detect gitmodules files - - commit 159e7b080bfa5d34559467cacaa79df89a01afc0 upstream. - - In preparation for performing fsck checks on .gitmodules - files, this commit plumbs in the actual detection of the - files. Note that unlike most other fsck checks, this cannot - be a property of a single object: we must know that the - object is found at a ".gitmodules" path at the root tree of - a commit. - - Since the fsck code only sees one object at a time, we have - to mark the related objects to fit the puzzle together. When - we see a commit we mark its tree as a root tree, and when - we see a root tree with a .gitmodules file, we mark the - corresponding blob to be checked. - - In an ideal world, we'd check the objects in topological - order: commits followed by trees followed by blobs. In that - case we can avoid ever loading an object twice, since all - markings would be complete by the time we get to the marked - objects. And indeed, if we are checking a single packfile, - this is the order in which Git will generally write the - objects. But we can't count on that: - - 1. git-fsck may show us the objects in arbitrary order - (loose objects are fed in sha1 order, but we may also - have multiple packs, and we process each pack fully in - sequence). - - 2. The type ordering is just what git-pack-objects happens - to write now. The pack format does not require a - specific order, and it's possible that future versions - of Git (or a custom version trying to fool official - Git's fsck checks!) may order it differently. - - 3. We may not even be fscking all of the relevant objects - at once. Consider pushing with transfer.fsckObjects, - where one push adds a blob at path "foo", and then a - second push adds the same blob at path ".gitmodules". - The blob is not part of the second push at all, but we - need to mark and check it. - - So in the general case, we need to make up to three passes - over the objects: once to make sure we've seen all commits, - then once to cover any trees we might have missed, and then - a final pass to cover any .gitmodules blobs we found in the - second pass. - - We can simplify things a bit by loosening the requirement - that we find .gitmodules only at root trees. Technically - a file like "subdir/.gitmodules" is not parsed by Git, but - it's not unreasonable for us to declare that Git is aware of - all ".gitmodules" files and make them eligible for checking. - That lets us drop the root-tree requirement, which - eliminates one pass entirely. And it makes our worst case - much better: instead of potentially queueing every root tree - to be re-examined, the worst case is that we queue each - unique .gitmodules blob for a second look. - - This patch just adds the boilerplate to find .gitmodules - files. The actual content checks will come in a subsequent - commit. - - [jn: backported to 2.1.y: - - using error_func instead of report to report fsck errors - - using sha1s instead of struct object_id - - using "struct hashmap" directly since "struct oidset" isn't - available] - - Signed-off-by: Jeff King - Signed-off-by: Jonathan Nieder - -commit f8c9e1358806fc649c48f0a80ea69c0c2f7c9ef2 -Author: Karsten Blees -Date: Thu Jul 3 00:22:11 2014 +0200 - - hashmap: add simplified hashmap_get_from_hash() API - - Hashmap entries are typically looked up by just a key. The hashmap_get() - API expects an initialized entry structure instead, to support compound - keys. This flexibility is currently only needed by find_dir_entry() in - name-hash.c (and compat/win32/fscache.c in the msysgit fork). All other - (currently five) call sites of hashmap_get() have to set up a near emtpy - entry structure, resulting in duplicate code like this: - - struct hashmap_entry keyentry; - hashmap_entry_init(&keyentry, hash(key)); - return hashmap_get(map, &keyentry, key); - - Add a hashmap_get_from_hash() API that allows hashmap lookups by just - specifying the key and its hash code, i.e.: - - return hashmap_get_from_hash(map, hash(key), key); - - Signed-off-by: Karsten Blees - Signed-off-by: Junio C Hamano - -commit 4c8e18c35a61140fa4cae20c66b8783a677c58cc -Author: Karsten Blees -Date: Thu Jul 3 00:20:20 2014 +0200 - - hashmap: factor out getting a hash code from a SHA1 - - Copying the first bytes of a SHA1 is duplicated in six places, - however, the implications (the actual value would depend on the - endianness of the platform) is documented only once. - - Add a properly documented API for this. - - [Dropping non-hashmap.[ch] portions of this patch, as a prepreq for - 159e7b080bfa5d34559467cacaa79df89a01afc0 "fsck: detect gitmodules - files" which uses the hashmap implementation. --sbeattie] - - Signed-off-by: Karsten Blees - Signed-off-by: Junio C Hamano - -commit b3f33dc3b1da719027c946b748ad959ea03cb605 -Author: Karsten Blees -Date: Wed Dec 18 14:41:27 2013 +0100 - - hashmap.h: use 'unsigned int' for hash-codes everywhere - - Signed-off-by: Karsten Blees - Signed-off-by: Junio C Hamano - -commit 53b42170beb29fab2378db8fca1455a825329eef -Author: Karsten Blees -Date: Thu Nov 14 20:17:54 2013 +0100 - - add a hashtable implementation that supports O(1) removal - - The existing hashtable implementation (in hash.[ch]) uses open addressing - (i.e. resolve hash collisions by distributing entries across the table). - Thus, removal is difficult to implement with less than O(n) complexity. - Resolving collisions of entries with identical hashes (e.g. via chaining) - is left to the client code. - - Add a hashtable implementation that supports O(1) removal and is slightly - easier to use due to builtin entry chaining. - - Supports all basic operations init, free, get, add, remove and iteration. - - Also includes ready-to-use hash functions based on the public domain FNV-1 - algorithm (http://www.isthe.com/chongo/tech/comp/fnv). - - The per-entry data structure (hashmap_entry) is piggybacked in front of - the client's data structure to save memory. See test-hashmap.c for usage - examples. - - The hashtable is resized by a factor of four when 80% full. With these - settings, average memory consumption is about 2/3 of hash.[ch], and - insertion is about twice as fast due to less frequent resizing. - - Lookups are also slightly faster, because entries are strictly confined to - their bucket (i.e. no data of other buckets needs to be traversed). - - Signed-off-by: Karsten Blees - Signed-off-by: Junio C Hamano - -commit 726f4913b8040c3b92a70f1c84e53c1f9bce3b8e -Author: Jeff King -Date: Wed May 2 15:44:51 2018 -0400 - - fsck: actually fsck blob data - - commit 7ac4f3a007e2567f9d2492806186aa063f9a08d6 upstream. - - Because fscking a blob has always been a noop, we didn't - bother passing around the blob data. In preparation for - content-level checks, let's fix up a few things: - - 1. The fsck_object() function just returns success for any - blob. Let's a noop fsck_blob(), which we can fill in - with actual logic later. - - 2. The fsck_loose() function in builtin/fsck.c - just threw away blob content after loading it. Let's - hold onto it until after we've called fsck_object(). - - The easiest way to do this is to just drop the - parse_loose_object() helper entirely. Incidentally, - this also fixes a memory leak: if we successfully - loaded the object data but did not parse it, we would - have left the function without freeing it. - - 3. When fsck_loose() loads the object data, it - does so with a custom read_loose_object() helper. This - function streams any blobs, regardless of size, under - the assumption that we're only checking the sha1. - - Instead, let's actually load blobs smaller than - big_file_threshold, as the normal object-reading - code-paths would do. This lets us fsck small files, and - a NULL return is an indication that the blob was so big - that it needed to be streamed, and we can pass that - information along to fsck_blob(). - - Signed-off-by: Jeff King - Signed-off-by: Jonathan Nieder - -commit e2ba4c21e6effb57fb8b123fb7afec8d5de69959 -Author: Johannes Schindelin -Date: Wed Sep 10 15:52:51 2014 +0200 - - fsck_object(): allow passing object data separately from the object itself - - commits 90a398bbd72477d5d228818db5665fdfcf13431b and - 4d0d89755e82c40df88cf94d84031978f8eac827 upstream. - - When fsck'ing an incoming pack, we need to fsck objects that cannot be - read via read_sha1_file() because they are not local yet (and might even - be rejected if transfer.fsckobjects is set to 'true'). - - For commits, there is a hack in place: we basically cache commit - objects' buffers anyway, but the same is not true, say, for tag objects. - - By refactoring fsck_object() to take the object buffer and size as - optional arguments -- optional, because we still fall back to the - previous method to look at the cached commit objects if the caller - passes NULL -- we prepare the machinery for the upcoming handling of tag - objects. - - The assumption that such buffers are inherently NUL terminated is now - wrong, so make sure that there is at least an empty line in the buffer. - That way, our checks would fail if the empty line was encountered - prematurely, and consequently we can get away with the current string - comparisons even with non-NUL-terminated buffers are passed to - fsck_object(). - - Signed-off-by: Johannes Schindelin - Signed-off-by: Junio C Hamano - Signed-off-by: Jonathan Nieder - -commit cfaa863b28379e5fb3f1acb871357a5ec85d4844 -Author: Jeff King -Date: Wed May 2 16:37:09 2018 -0400 - - index-pack: make fsck error message more specific - - commit db5a58c1bda5b20169b9958af1e8b05ddd178b01 upstream. - - If fsck reports an error, we say only "Error in object". - This isn't quite as bad as it might seem, since the fsck - code would have dumped some errors to stderr already. But it - might help to give a little more context. The earlier output - would not have even mentioned "fsck", and that may be a clue - that the "fsck.*" or "*.fsckObjects" config may be relevant. - - Signed-off-by: Jeff King - Signed-off-by: Jonathan Nieder - -commit a828c426b337f0e519bc80acad5b29616458333e -Author: Jeff King -Date: Fri Jan 13 12:59:44 2017 -0500 - - fsck: parse loose object paths directly - - commit c68b489e56431cf27f7719913ab09ddc62f95912 upstream. - - When we iterate over the list of loose objects to check, we - get the actual path of each object. But we then throw it - away and pass just the sha1 to fsck_sha1(), which will do a - fresh lookup. Usually it would find the same object, but it - may not if an object exists both as a loose and a packed - object. We may end up checking the packed object twice, and - never look at the loose one. - - In practice this isn't too terrible, because if fsck doesn't - complain, it means you have at least one good copy. But - since the point of fsck is to look for corruption, we should - be thorough. - - The new read_loose_object() interface can help us get the - data from disk, and then we replace parse_object() with - parse_object_buffer(). As a bonus, our error messages now - mention the path to a corrupted object, which should make it - easier to track down errors when they do happen. - - [jn: backported by passing path through the call chain to - fsck_loose, since until v2.7.0-rc0~68^2~4 (fsck: use - for_each_loose_file_in_objdir, 2015-09-24) it is not - available there] - - Signed-off-by: Jeff King - Signed-off-by: Junio C Hamano - Signed-off-by: Jonathan Nieder - -commit 902125f4a8ed87ed9d9df3dd6af963e828bf4f79 -Author: Jeff King -Date: Thu Sep 24 17:08:28 2015 -0400 - - fsck: drop inode-sorting code - - commit 144e4cf7092ee8cff44e9c7600aaa7515ad6a78f upstream. - - Fsck tries to access loose objects in order of inode number, - with the hope that this would make cold cache access faster - on a spinning disk. This dates back to 7e8c174 (fsck-cache: - sort entries by inode number, 2005-05-02), which predates - the invention of packfiles. - - These days, there's not much point in trying to optimize - cold cache for a large number of loose objects. You are much - better off to simply pack the objects, which will reduce the - disk footprint _and_ provide better locality of data access. - - So while you can certainly construct pathological cases - where this code might help, it is not worth the trouble - anymore. - - [backport to 1.9.x> include t1450-fsck.sh changes from commit - 2e770fe47ef9c0b20bc687e37f3eb50f1bf919d0 as the change propogates - returning failure for git fsck. --sbeattie] - - Signed-off-by: Jeff King - Signed-off-by: Junio C Hamano - Signed-off-by: Jonathan Nieder - -commit e2bf202e0f46789ebec25ee8584455ac4b9e8ee6 -Author: Jeff King -Date: Fri Jan 13 12:58:16 2017 -0500 - - sha1_file: add read_loose_object() function - - commit f6371f9210418f1beabc85b097e2a3470aeeb54d upstream. - - It's surprisingly hard to ask the sha1_file code to open a - _specific_ incarnation of a loose object. Most of the - functions take a sha1, and loop over the various object - types (packed versus loose) and locations (local versus - alternates) at a low level. - - However, some tools like fsck need to look at a specific - file. This patch gives them a function they can use to open - the loose object at a given path. - - The implementation unfortunately ends up repeating bits of - related functions, but there's not a good way around it - without some major refactoring of the whole sha1_file stack. - We need to mmap the specific file, then partially read the - zlib stream to know whether we're streaming or not, and then - finally either stream it or copy the data to a buffer. - - We can do that by assembling some of the more arcane - internal sha1_file functions, but we end up having to - essentially reimplement unpack_sha1_file(), along with the - streaming bits of check_sha1_signature(). - - Still, most of the ugliness is contained in the new - function, and the interface is clean enough that it may be - reusable (though it seems unlikely anything but git-fsck - would care about opening a specific file). - - Signed-off-by: Jeff King - Signed-off-by: Junio C Hamano - Signed-off-by: Jonathan Nieder - -commit 15508d17089bef577fe32e4ae031f24bf6274f0a -Author: Jeff King -Date: Fri May 4 20:03:35 2018 -0400 - - verify_path: disallow symlinks in .gitmodules - - commit 10ecfa76491e4923988337b2e2243b05376b40de upstream. - - There are a few reasons it's not a good idea to make - .gitmodules a symlink, including: - - 1. It won't be portable to systems without symlinks. - - 2. It may behave inconsistently, since Git may look at - this file in the index or a tree without bothering to - resolve any symbolic links. We don't do this _yet_, but - the config infrastructure is there and it's planned for - the future. - - With some clever code, we could make (2) work. And some - people may not care about (1) if they only work on one - platform. But there are a few security reasons to simply - disallow it: - - a. A symlinked .gitmodules file may circumvent any fsck - checks of the content. - - b. Git may read and write from the on-disk file without - sanity checking the symlink target. So for example, if - you link ".gitmodules" to "../oops" and run "git - submodule add", we'll write to the file "oops" outside - the repository. - - Again, both of those are problems that _could_ be solved - with sufficient code, but given the complications in (1) and - (2), we're better off just outlawing it explicitly. - - Note the slightly tricky call to verify_path() in - update-index's update_one(). There we may not have a mode if - we're not updating from the filesystem (e.g., we might just - be removing the file). Passing "0" as the mode there works - fine; since it's not a symlink, we'll just skip the extra - checks. - - Signed-off-by: Jeff King - Signed-off-by: Jonathan Nieder - -commit 38b09a0a75948ecda0ae5dd1161498f7d09a957f -Author: Jeff King -Date: Mon May 14 11:00:56 2018 -0400 - - update-index: stat updated files earlier - - commit eb12dd0c764d2b71bebd5ffffb7379a3835253ae upstream. - - In the update_one(), we check verify_path() on the proposed - path before doing anything else. In preparation for having - verify_path() look at the file mode, let's stat the file - earlier, so we can check the mode accurately. - - This is made a bit trickier by the fact that this function - only does an lstat in a few code paths (the ones that flow - down through process_path()). So we can speculatively do the - lstat() here and pass the results down, and just use a dummy - mode for cases where we won't actually be updating the index - from the filesystem. - - Signed-off-by: Jeff King - Signed-off-by: Jonathan Nieder - -commit 5d0dff51f2ffd04b64532ac391fcc6d3193f4a9c -Author: Jeff King -Date: Tue May 15 09:56:50 2018 -0400 - - verify_dotfile: mention case-insensitivity in comment - - commit 641084b618ddbe099f0992161988c3e479ae848b upstream. - - We're more restrictive than we need to be in matching ".GIT" - on case-sensitive filesystems; let's make a note that this - is intentional. - - Signed-off-by: Jeff King - Signed-off-by: Jonathan Nieder - -commit 887ef4437acc375084de58b342e0e46e29610725 -Author: Jeff King -Date: Sun May 13 13:00:23 2018 -0400 - - verify_path: drop clever fallthrough - - commit e19e5e66d691bdeeeb5e0ed2ffcecdd7666b0d7b upstream. - - We check ".git" and ".." in the same switch statement, and - fall through the cases to share the end-of-component check. - While this saves us a line or two, it makes modifying the - function much harder. Let's just write it out. - - Signed-off-by: Jeff King - Signed-off-by: Jonathan Nieder - -commit b80dad3b02a1a108af37788f31ee418faed7595c -Author: Jeff King -Date: Sun May 13 12:57:14 2018 -0400 - - skip_prefix: add case-insensitive variant - - commit 41a80924aec0e94309786837b6f954a3b3f19b71 upstream. - - We have the convenient skip_prefix() helper, but if you want - to do case-insensitive matching, you're stuck doing it by - hand. We could add an extra parameter to the function to - let callers ask for this, but the function is small and - somewhat performance-critical. Let's just re-implement it - for the case-insensitive version. - - Signed-off-by: Jeff King - Signed-off-by: Jonathan Nieder - -commit 5457d4cbd138a9642115f4449f42ab9317c5f1af -Author: Jeff King -Date: Mon Apr 30 03:25:25 2018 -0400 - - submodule-config: verify submodule names as paths - - commit 0383bbb9015898cbc79abd7b64316484d7713b44 upstream. - - Submodule "names" come from the untrusted .gitmodules file, - but we blindly append them to $GIT_DIR/modules to create our - on-disk repo paths. This means you can do bad things by - putting "../" into the name (among other things). - - Let's sanity-check these names to avoid building a path that - can be exploited. There are two main decisions: - - 1. What should the allowed syntax be? - - It's tempting to reuse verify_path(), since submodule - names typically come from in-repo paths. But there are - two reasons not to: - - a. It's technically more strict than what we need, as - we really care only about breaking out of the - $GIT_DIR/modules/ hierarchy. E.g., having a - submodule named "foo/.git" isn't actually - dangerous, and it's possible that somebody has - manually given such a funny name. - - b. Since we'll eventually use this checking logic in - fsck to prevent downstream repositories, it should - be consistent across platforms. Because - verify_path() relies on is_dir_sep(), it wouldn't - block "foo\..\bar" on a non-Windows machine. - - 2. Where should we enforce it? These days most of the - .gitmodules reads go through submodule-config.c, so - I've put it there in the reading step. That should - cover all of the C code. - - We also construct the name for "git submodule add" - inside the git-submodule.sh script. This is probably - not a big deal for security since the name is coming - from the user anyway, but it would be polite to remind - them if the name they pick is invalid (and we need to - expose the name-checker to the shell anyway for our - test scripts). - - This patch issues a warning when reading .gitmodules - and just ignores the related config entry completely. - This will generally end up producing a sensible error, - as it works the same as a .gitmodules file which is - missing a submodule entry (so "submodule update" will - barf, but "git clone --recurse-submodules" will print - an error but not abort the clone. - - There is one minor oddity, which is that we print the - warning once per malformed config key (since that's how - the config subsystem gives us the entries). So in the - new test, for example, the user would see three - warnings. That's OK, since the intent is that this case - should never come up outside of malicious repositories - (and then it might even benefit the user to see the - message multiple times). - - Credit for finding this vulnerability and the proof of - concept from which the test script was adapted goes to - Etienne Stalmans. - - [jn: backported to 2.1.y: - - adding a skeletal git submodule--helper command to house - the new check-name subcommand. The full submodule--helper - was not introduced until v2.7.0-rc0~136^2~2 (submodule: - rewrite `module_list` shell function in C, 2015-09-02). - - calling 'git submodule--helper check-name' to validate - submodule names in git-submodule.sh::module_name(). That - shell function was rewritten in C in v2.7.0-rc0~136^2~1 - (submodule: rewrite `module_name` shell function in C, - 2015-09-02). - - propagating the error from module_name in cmd_foreach. - Without that change, the script passed to 'git submodule - foreach' would see an empty $name for submodules with - invalid name. The same bug still exists in v2.17.1. - - ported the checks in C from the submodule-config API - introduced in v2.6.0-rc0~24^2~3 (submodule: implement a - config API for lookup of .gitmodules values, 2015-08-17) - to the older submodule API. - - the original patch expects 'git clone' to succeed in the - test because v2.13.0-rc0~10^2~3 (clone: teach - --recurse-submodules to optionally take a pathspec, - 2017-03-17) makes 'git clone' skip invalid submodules. - Updated the test to pass in older Git versions where the - submodule name check makes 'git clone' fail.] - - Signed-off-by: Jeff King - Signed-off-by: Jonathan Nieder - -commit eca4704a95f052e903b18c4fddf378104741fcbc -Author: Junio C Hamano -Date: Thu Jan 29 12:41:22 2015 -0800 - - apply: do not touch a file beyond a symbolic link - - commit e0d201b61601e17e24ed00cc3d16e8e25ca68596 upstream. - - Because Git tracks symbolic links as symbolic links, a path that - has a symbolic link in its leading part (e.g. path/to/dir/file, - where path/to/dir is a symbolic link to somewhere else, be it - inside or outside the working tree) can never appear in a patch - that validly applies, unless the same patch first removes the - symbolic link to allow a directory to be created there. - - Detect and reject such a patch. - - Things to note: - - - Unfortunately, we cannot reuse the has_symlink_leading_path() - from dir.c, as that is only about the working tree, but "git - apply" can be told to apply the patch only to the index or to - both the index and to the working tree. - - - We cannot directly use has_symlink_leading_path() even when we - are applying only to the working tree, as an early patch of a - valid input may remove a symbolic link path/to/dir and then a - later patch of the input may create a path path/to/dir/file, but - "git apply" first checks the input without touching either the - index or the working tree. The leading symbolic link check must - be done on the interim result we compute in-core (i.e. after the - first patch, there is no path/to/dir symbolic link and it is - perfectly valid to create path/to/dir/file). - - Similarly, when an input creates a symbolic link path/to/dir and - then creates a file path/to/dir/file, we need to flag it as an - error without actually creating path/to/dir symbolic link in the - filesystem. - - Instead, for any patch in the input that leaves a path (i.e. a non - deletion) in the result, we check all leading paths against the - resulting tree that the patch would create by inspecting all the - patches in the input and then the target of patch application - (either the index or the working tree). - - This way, we catch a mischief or a mistake to add a symbolic link - path/to/dir and a file path/to/dir/file at the same time, while - allowing a valid patch that removes a symbolic link path/to/dir and - then adds a file path/to/dir/file. - - Signed-off-by: Junio C Hamano - Signed-off-by: Jonathan Nieder - -commit 1b375180858c0fa786fe579a713df1f12728f1dc -Author: Junio C Hamano -Date: Fri Jan 30 15:34:13 2015 -0800 - - apply: do not read from beyond a symbolic link - - commit fdc2c3a926c21e24986677abd02c8bc568a5de32 upstream. - - We should reject a patch, whether it renames/copies dir/file to - elsewhere with or without modificiation, or updates dir/file in - place, if "dir/" part is actually a symbolic link to elsewhere, - by making sure that the code to read the preimage does not read - from a path that is beyond a symbolic link. - - Signed-off-by: Junio C Hamano - Signed-off-by: Jonathan Nieder - -commit 6e9f81cf425af97da742914abf877f2dbc0a650f -Author: Junio C Hamano -Date: Fri Jan 30 15:15:59 2015 -0800 - - apply: do not read from the filesystem under --index - - commit 3c37a2e339e695c7cc41048fe0921cbc8b48b0f0 upstream. - - We currently read the preimage to apply a patch from the index only - when the --cached option is given. Do so also when the command is - running under the --index option. With --index, the index entry and - the working tree file for a path that is involved in a patch must be - identical, so this should not affect the result, but by reading from - the index, we will get the protection to avoid reading an unintended - path beyond a symbolic link automatically. - - Signed-off-by: Junio C Hamano - Signed-off-by: Jonathan Nieder - -commit 714bced2aa3ccc8b762f79c08443b246d2440402 -Author: Junio C Hamano -Date: Thu Jan 29 15:35:24 2015 -0800 - - apply: reject input that touches outside the working area - - commit c536c0755f6450b7bcce499cfda171f8c6d1e593 upstream. - - By default, a patch that affects outside the working area (either a - Git controlled working tree, or the current working directory when - "git apply" is used as a replacement of GNU patch) is rejected as a - mistake (or a mischief). Git itself does not create such a patch, - unless the user bends over backwards and specifies a non-standard - prefix to "git diff" and friends. - - When `git apply` is used as a "better GNU patch", the user can pass - the `--unsafe-paths` option to override this safety check. This - option has no effect when `--index` or `--cached` is in use. - - The new test was stolen from Jeff King with slight enhancements. - Note that a few new tests for touching outside the working area by - following a symbolic link are still expected to fail at this step, - but will be fixed in later steps. - - Signed-off-by: Junio C Hamano - Signed-off-by: Jonathan Nieder - -commit be3b8a3011560ead1a87da813b6e25ece3cf94b6 -Author: Jeff King -Date: Mon Aug 26 17:57:18 2013 -0400 - - config: do not use C function names as struct members - - According to C99, section 7.1.4: - - Any function declared in a header may be additionally - implemented as a function-like macro defined in the - header. - - Therefore calling our struct member function pointer "fgetc" - may run afoul of unwanted macro expansion when we call: - - char c = cf->fgetc(cf); - - This turned out to be a problem on uclibc, which defines - fgetc as a macro and causes compilation failure. - - The standard suggests fixing this in a few ways: - - 1. Using extra parentheses to inhibit the function-like - macro expansion. E.g., "(cf->fgetc)(cf)". This is - undesirable as it's ugly, and each call site needs to - remember to use it (and on systems without the macro, - forgetting will compile just fine). - - 2. Using #undef (because a conforming implementation must - also be providing fgetc as a function). This is - undesirable because presumably the implementation was - using the macro for a performance benefit, and we are - dropping that optimization. - - Instead, we can simply use non-colliding names. - - Signed-off-by: Jeff King - Signed-off-by: Junio C Hamano - -commit eb937a70aaded50b0f2d194adb2917881bda129b -Author: Heiko Voigt -Date: Fri Jul 12 00:48:30 2013 +0200 - - do not die when error in config parsing of buf occurs - - If a config parsing error in a file occurs we can die and let the user - fix the issue. This is different for the buf parsing function since it - can be used to parse blobs of .gitmodules files. If a parsing error - occurs here we should proceed since otherwise a database containing such - an error in a single revision could be rendered unusable. - - Signed-off-by: Heiko Voigt - Acked-by: Jeff King - Signed-off-by: Junio C Hamano - -commit 476b7567d81fffb6d5f84d26acfa3e44df67c4e1 -Author: Heiko Voigt -Date: Fri Jul 12 00:46:47 2013 +0200 - - teach config --blob option to parse config from database - - This can be used to read configuration values directly from git's - database. For example it is useful for reading to be checked out - .gitmodules files directly from the database. - - Signed-off-by: Heiko Voigt - Acked-by: Jeff King - Signed-off-by: Junio C Hamano - -commit db9d2efbf64725c4891db20a571623c846033d70 -Author: Heiko Voigt -Date: Fri Jul 12 00:44:39 2013 +0200 - - config: make parsing stack struct independent from actual data source - - To simplify adding other sources we extract all functions needed for - parsing into a list of callbacks. We implement those callbacks for the - current file parsing. A new source can implement its own set of callbacks. - - Instead of storing the concrete FILE pointer for parsing we store a void - pointer. A new source can use this to store its custom data. - - Signed-off-by: Heiko Voigt - Acked-by: Jeff King - Signed-off-by: Junio C Hamano - -commit 345dcdb95bc8e96ded9a0d0da7c9777de7bb9290 -Author: Heiko Voigt -Date: Sat May 11 15:19:29 2013 +0200 - - config: drop cf validity check in get_next_char() - - The global variable cf is set with an initialized value in all codepaths before - calling this function. - - The complete call graph looks like this: - - git_config_from_file - -> do_config_from - -> git_parse_file - -> get_next_char - -> get_value - -> get_next_char - -> parse_value - -> get_next_char - -> get_base_var - -> get_next_char - -> get_extended_base_var - -> get_next_char - - The variable is initialized in do_config_from. - - Signed-off-by: Heiko Voigt - Acked-by: Jeff King - Signed-off-by: Junio C Hamano - -commit 30bacff11067cd4d42e4d57ebe691c8f8eb1e570 -Author: Heiko Voigt -Date: Sat May 11 15:18:52 2013 +0200 - - config: factor out config file stack management - - Because a config callback may start parsing a new file, the - global context regarding the current config file is stored - as a stack. Currently we only need to manage that stack from - git_config_from_file. Let's factor it out to allow new - sources of config data. - - Signed-off-by: Heiko Voigt - Acked-by: Jeff King - Signed-off-by: Junio C Hamano ---- - .gitignore | 1 + - Documentation/git-apply.txt | 12 +- - Documentation/git-config.txt | 7 + - Documentation/technical/api-hashmap.txt | 249 ++++++++++++++++++++++++ - Makefile | 4 + - builtin.h | 1 + - builtin/apply.c | 142 +++++++++++++- - builtin/config.c | 31 ++- - builtin/fsck.c | 165 +++++++--------- - builtin/index-pack.c | 13 +- - builtin/submodule--helper.c | 35 ++++ - builtin/unpack-objects.c | 21 +- - builtin/update-index.c | 31 +-- - cache.h | 21 +- - config.c | 217 ++++++++++++++++----- - fsck.c | 193 +++++++++++++++++- - fsck.h | 11 +- - git-compat-util.h | 17 ++ - git-submodule.sh | 21 +- - git.c | 1 + - hashmap.c | 228 ++++++++++++++++++++++ - hashmap.h | 90 +++++++++ - read-cache.c | 30 ++- - sha1_file.c | 132 ++++++++++++- - submodule.c | 29 +++ - submodule.h | 7 + - t/lib-pack.sh | 110 +++++++++++ - t/t0011-hashmap.sh | 240 +++++++++++++++++++++++ - t/t1307-config-blob.sh | 70 +++++++ - t/t1450-fsck.sh | 48 ++++- - t/t4122-apply-symlink-inside.sh | 106 ++++++++++ - t/t4139-apply-escape.sh | 141 ++++++++++++++ - t/t7415-submodule-names.sh | 154 +++++++++++++++ - test-hashmap.c | 335 ++++++++++++++++++++++++++++++++ - 34 files changed, 2717 insertions(+), 196 deletions(-) - create mode 100644 Documentation/technical/api-hashmap.txt - create mode 100644 builtin/submodule--helper.c - create mode 100644 hashmap.c - create mode 100644 hashmap.h - create mode 100644 t/lib-pack.sh - create mode 100755 t/t0011-hashmap.sh - create mode 100755 t/t1307-config-blob.sh - create mode 100755 t/t4139-apply-escape.sh - create mode 100755 t/t7415-submodule-names.sh - create mode 100644 test-hashmap.c - -diff --git a/.gitignore b/.gitignore -index 6669bf0..92b0483 100644 ---- a/.gitignore -+++ b/.gitignore -@@ -154,6 +154,7 @@ - /git-status - /git-stripspace - /git-submodule -+/git-submodule--helper - /git-svn - /git-symbolic-ref - /git-tag -diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt -index f605327..9489664 100644 ---- a/Documentation/git-apply.txt -+++ b/Documentation/git-apply.txt -@@ -16,7 +16,7 @@ SYNOPSIS - [--ignore-space-change | --ignore-whitespace ] - [--whitespace=(nowarn|warn|fix|error|error-all)] - [--exclude=] [--include=] [--directory=] -- [--verbose] [...] -+ [--verbose] [--unsafe-paths] [...] - - DESCRIPTION - ----------- -@@ -229,6 +229,16 @@ For example, a patch that talks about updating `a/git-gui.sh` to `b/git-gui.sh` - can be applied to the file in the working tree `modules/git-gui/git-gui.sh` by - running `git apply --directory=modules/git-gui`. - -+--unsafe-paths:: -+ By default, a patch that affects outside the working area -+ (either a Git controlled working tree, or the current working -+ directory when "git apply" is used as a replacement of GNU -+ patch) is rejected as a mistake (or a mischief). -++ -+When `git apply` is used as a "better GNU patch", the user can pass -+the `--unsafe-paths` option to override this safety check. This option -+has no effect when `--index` or `--cached` is in use. -+ - Configuration - ------------- - -diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt -index d88a6fc..3a4ed10 100644 ---- a/Documentation/git-config.txt -+++ b/Documentation/git-config.txt -@@ -118,6 +118,13 @@ See also <>. - --file config-file:: - Use the given config file instead of the one specified by GIT_CONFIG. - -+--blob blob:: -+ Similar to '--file' but use the given blob instead of a file. E.g. -+ you can use 'master:.gitmodules' to read values from the file -+ '.gitmodules' in the master branch. See "SPECIFYING REVISIONS" -+ section in linkgit:gitrevisions[7] for a more complete list of -+ ways to spell blob names. -+ - --remove-section:: - Remove the given section from the configuration file. - -diff --git a/Documentation/technical/api-hashmap.txt b/Documentation/technical/api-hashmap.txt -new file mode 100644 -index 0000000..0249b50 ---- /dev/null -+++ b/Documentation/technical/api-hashmap.txt -@@ -0,0 +1,249 @@ -+hashmap API -+=========== -+ -+The hashmap API is a generic implementation of hash-based key-value mappings. -+ -+Data Structures -+--------------- -+ -+`struct hashmap`:: -+ -+ The hash table structure. -++ -+The `size` member keeps track of the total number of entries. The `cmpfn` -+member is a function used to compare two entries for equality. The `table` and -+`tablesize` members store the hash table and its size, respectively. -+ -+`struct hashmap_entry`:: -+ -+ An opaque structure representing an entry in the hash table, which must -+ be used as first member of user data structures. Ideally it should be -+ followed by an int-sized member to prevent unused memory on 64-bit -+ systems due to alignment. -++ -+The `hash` member is the entry's hash code and the `next` member points to the -+next entry in case of collisions (i.e. if multiple entries map to the same -+bucket). -+ -+`struct hashmap_iter`:: -+ -+ An iterator structure, to be used with hashmap_iter_* functions. -+ -+Types -+----- -+ -+`int (*hashmap_cmp_fn)(const void *entry, const void *entry_or_key, const void *keydata)`:: -+ -+ User-supplied function to test two hashmap entries for equality. Shall -+ return 0 if the entries are equal. -++ -+This function is always called with non-NULL `entry` / `entry_or_key` -+parameters that have the same hash code. When looking up an entry, the `key` -+and `keydata` parameters to hashmap_get and hashmap_remove are always passed -+as second and third argument, respectively. Otherwise, `keydata` is NULL. -+ -+Functions -+--------- -+ -+`unsigned int strhash(const char *buf)`:: -+`unsigned int strihash(const char *buf)`:: -+`unsigned int memhash(const void *buf, size_t len)`:: -+`unsigned int memihash(const void *buf, size_t len)`:: -+ -+ Ready-to-use hash functions for strings, using the FNV-1 algorithm (see -+ http://www.isthe.com/chongo/tech/comp/fnv). -++ -+`strhash` and `strihash` take 0-terminated strings, while `memhash` and -+`memihash` operate on arbitrary-length memory. -++ -+`strihash` and `memihash` are case insensitive versions. -+ -+`void hashmap_init(struct hashmap *map, hashmap_cmp_fn equals_function, size_t initial_size)`:: -+ -+ Initializes a hashmap structure. -++ -+`map` is the hashmap to initialize. -++ -+The `equals_function` can be specified to compare two entries for equality. -+If NULL, entries are considered equal if their hash codes are equal. -++ -+If the total number of entries is known in advance, the `initial_size` -+parameter may be used to preallocate a sufficiently large table and thus -+prevent expensive resizing. If 0, the table is dynamically resized. -+ -+`void hashmap_free(struct hashmap *map, int free_entries)`:: -+ -+ Frees a hashmap structure and allocated memory. -++ -+`map` is the hashmap to free. -++ -+If `free_entries` is true, each hashmap_entry in the map is freed as well -+(using stdlib's free()). -+ -+`void hashmap_entry_init(void *entry, int hash)`:: -+ -+ Initializes a hashmap_entry structure. -++ -+`entry` points to the entry to initialize. -++ -+`hash` is the hash code of the entry. -+ -+`void *hashmap_get(const struct hashmap *map, const void *key, const void *keydata)`:: -+ -+ Returns the hashmap entry for the specified key, or NULL if not found. -++ -+`map` is the hashmap structure. -++ -+`key` is a hashmap_entry structure (or user data structure that starts with -+hashmap_entry) that has at least been initialized with the proper hash code -+(via `hashmap_entry_init`). -++ -+If an entry with matching hash code is found, `key` and `keydata` are passed -+to `hashmap_cmp_fn` to decide whether the entry matches the key. -+ -+`void *hashmap_get_from_hash(const struct hashmap *map, unsigned int hash, const void *keydata)`:: -+ -+ Returns the hashmap entry for the specified hash code and key data, -+ or NULL if not found. -++ -+`map` is the hashmap structure. -++ -+`hash` is the hash code of the entry to look up. -++ -+If an entry with matching hash code is found, `keydata` is passed to -+`hashmap_cmp_fn` to decide whether the entry matches the key. The -+`entry_or_key` parameter points to a bogus hashmap_entry structure that -+should not be used in the comparison. -+ -+`void *hashmap_get_next(const struct hashmap *map, const void *entry)`:: -+ -+ Returns the next equal hashmap entry, or NULL if not found. This can be -+ used to iterate over duplicate entries (see `hashmap_add`). -++ -+`map` is the hashmap structure. -++ -+`entry` is the hashmap_entry to start the search from, obtained via a previous -+call to `hashmap_get` or `hashmap_get_next`. -+ -+`void hashmap_add(struct hashmap *map, void *entry)`:: -+ -+ Adds a hashmap entry. This allows to add duplicate entries (i.e. -+ separate values with the same key according to hashmap_cmp_fn). -++ -+`map` is the hashmap structure. -++ -+`entry` is the entry to add. -+ -+`void *hashmap_put(struct hashmap *map, void *entry)`:: -+ -+ Adds or replaces a hashmap entry. If the hashmap contains duplicate -+ entries equal to the specified entry, only one of them will be replaced. -++ -+`map` is the hashmap structure. -++ -+`entry` is the entry to add or replace. -++ -+Returns the replaced entry, or NULL if not found (i.e. the entry was added). -+ -+`void *hashmap_remove(struct hashmap *map, const void *key, const void *keydata)`:: -+ -+ Removes a hashmap entry matching the specified key. If the hashmap -+ contains duplicate entries equal to the specified key, only one of -+ them will be removed. -++ -+`map` is the hashmap structure. -++ -+`key` is a hashmap_entry structure (or user data structure that starts with -+hashmap_entry) that has at least been initialized with the proper hash code -+(via `hashmap_entry_init`). -++ -+If an entry with matching hash code is found, `key` and `keydata` are -+passed to `hashmap_cmp_fn` to decide whether the entry matches the key. -++ -+Returns the removed entry, or NULL if not found. -+ -+`void hashmap_iter_init(struct hashmap *map, struct hashmap_iter *iter)`:: -+`void *hashmap_iter_next(struct hashmap_iter *iter)`:: -+`void *hashmap_iter_first(struct hashmap *map, struct hashmap_iter *iter)`:: -+ -+ Used to iterate over all entries of a hashmap. -++ -+`hashmap_iter_init` initializes a `hashmap_iter` structure. -++ -+`hashmap_iter_next` returns the next hashmap_entry, or NULL if there are no -+more entries. -++ -+`hashmap_iter_first` is a combination of both (i.e. initializes the iterator -+and returns the first entry, if any). -+ -+Usage example -+------------- -+ -+Here's a simple usage example that maps long keys to double values. -+[source,c] -+------------ -+struct hashmap map; -+ -+struct long2double { -+ struct hashmap_entry ent; /* must be the first member! */ -+ long key; -+ double value; -+}; -+ -+static int long2double_cmp(const struct long2double *e1, const struct long2double *e2, const void *unused) -+{ -+ return !(e1->key == e2->key); -+} -+ -+void long2double_init(void) -+{ -+ hashmap_init(&map, (hashmap_cmp_fn) long2double_cmp, 0); -+} -+ -+void long2double_free(void) -+{ -+ hashmap_free(&map, 1); -+} -+ -+static struct long2double *find_entry(long key) -+{ -+ struct long2double k; -+ hashmap_entry_init(&k, memhash(&key, sizeof(long))); -+ k.key = key; -+ return hashmap_get(&map, &k, NULL); -+} -+ -+double get_value(long key) -+{ -+ struct long2double *e = find_entry(key); -+ return e ? e->value : 0; -+} -+ -+void set_value(long key, double value) -+{ -+ struct long2double *e = find_entry(key); -+ if (!e) { -+ e = malloc(sizeof(struct long2double)); -+ hashmap_entry_init(e, memhash(&key, sizeof(long))); -+ e->key = key; -+ hashmap_add(&map, e); -+ } -+ e->value = value; -+} -+------------ -+ -+Using variable-sized keys -+------------------------- -+ -+The `hashmap_entry_get` and `hashmap_entry_remove` functions expect an ordinary -+`hashmap_entry` structure as key to find the correct entry. If the key data is -+variable-sized (e.g. a FLEX_ARRAY string) or quite large, it is undesirable -+to create a full-fledged entry structure on the heap and copy all the key data -+into the structure. -+ -+In this case, the `keydata` parameter can be used to pass -+variable-sized key data directly to the comparison function, and the `key` -+parameter can be a stripped-down, fixed size entry structure allocated on the -+stack. -+ -+See test-hashmap.c for an example using arbitrary-length strings as keys. -diff --git a/Makefile b/Makefile -index 0f931a2..daefb2f 100644 ---- a/Makefile -+++ b/Makefile -@@ -551,6 +551,7 @@ TEST_PROGRAMS_NEED_X += test-date - TEST_PROGRAMS_NEED_X += test-delta - TEST_PROGRAMS_NEED_X += test-dump-cache-tree - TEST_PROGRAMS_NEED_X += test-genrandom -+TEST_PROGRAMS_NEED_X += test-hashmap - TEST_PROGRAMS_NEED_X += test-index-version - TEST_PROGRAMS_NEED_X += test-line-buffer - TEST_PROGRAMS_NEED_X += test-match-trees -@@ -669,6 +670,7 @@ LIB_H += gpg-interface.h - LIB_H += graph.h - LIB_H += grep.h - LIB_H += hash.h -+LIB_H += hashmap.h - LIB_H += help.h - LIB_H += http.h - LIB_H += kwset.h -@@ -796,6 +798,7 @@ LIB_OBJS += gpg-interface.o - LIB_OBJS += graph.o - LIB_OBJS += grep.o - LIB_OBJS += hash.o -+LIB_OBJS += hashmap.o - LIB_OBJS += help.o - LIB_OBJS += hex.o - LIB_OBJS += ident.o -@@ -964,6 +967,7 @@ BUILTIN_OBJS += builtin/shortlog.o - BUILTIN_OBJS += builtin/show-branch.o - BUILTIN_OBJS += builtin/show-ref.o - BUILTIN_OBJS += builtin/stripspace.o -+BUILTIN_OBJS += builtin/submodule--helper.o - BUILTIN_OBJS += builtin/symbolic-ref.o - BUILTIN_OBJS += builtin/tag.o - BUILTIN_OBJS += builtin/tar-tree.o -diff --git a/builtin.h b/builtin.h -index faef559..c8330d9 100644 ---- a/builtin.h -+++ b/builtin.h -@@ -127,6 +127,7 @@ extern int cmd_show(int argc, const char **argv, const char *prefix); - extern int cmd_show_branch(int argc, const char **argv, const char *prefix); - extern int cmd_status(int argc, const char **argv, const char *prefix); - extern int cmd_stripspace(int argc, const char **argv, const char *prefix); -+extern int cmd_submodule__helper(int argc, const char **argv, const char *prefix); - extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix); - extern int cmd_tag(int argc, const char **argv, const char *prefix); - extern int cmd_tar_tree(int argc, const char **argv, const char *prefix); -diff --git a/builtin/apply.c b/builtin/apply.c -index 30eefc3..48e900d 100644 ---- a/builtin/apply.c -+++ b/builtin/apply.c -@@ -50,6 +50,7 @@ static int apply_verbosely; - static int allow_overlap; - static int no_add; - static int threeway; -+static int unsafe_paths; - static const char *fake_ancestor; - static int line_termination = '\n'; - static unsigned int p_context = UINT_MAX; -@@ -3135,7 +3136,7 @@ static int load_patch_target(struct strbuf *buf, - const char *name, - unsigned expected_mode) - { -- if (cached) { -+ if (cached || check_index) { - if (read_file_or_gitlink(ce, buf)) - return error(_("read of %s failed"), name); - } else if (name) { -@@ -3144,6 +3145,8 @@ static int load_patch_target(struct strbuf *buf, - return read_file_or_gitlink(ce, buf); - else - return SUBMODULE_PATCH_WITHOUT_INDEX; -+ } else if (has_symlink_leading_path(name, strlen(name))) { -+ return error(_("reading from '%s' beyond a symbolic link"), name); - } else { - if (read_old_data(st, name, buf)) - return error(_("read of %s failed"), name); -@@ -3482,6 +3485,121 @@ static int check_to_create(const char *new_name, int ok_if_exists) - return 0; - } - -+/* -+ * We need to keep track of how symlinks in the preimage are -+ * manipulated by the patches. A patch to add a/b/c where a/b -+ * is a symlink should not be allowed to affect the directory -+ * the symlink points at, but if the same patch removes a/b, -+ * it is perfectly fine, as the patch removes a/b to make room -+ * to create a directory a/b so that a/b/c can be created. -+ */ -+static struct string_list symlink_changes; -+#define SYMLINK_GOES_AWAY 01 -+#define SYMLINK_IN_RESULT 02 -+ -+static uintptr_t register_symlink_changes(const char *path, uintptr_t what) -+{ -+ struct string_list_item *ent; -+ -+ ent = string_list_lookup(&symlink_changes, path); -+ if (!ent) { -+ ent = string_list_insert(&symlink_changes, path); -+ ent->util = (void *)0; -+ } -+ ent->util = (void *)(what | ((uintptr_t)ent->util)); -+ return (uintptr_t)ent->util; -+} -+ -+static uintptr_t check_symlink_changes(const char *path) -+{ -+ struct string_list_item *ent; -+ -+ ent = string_list_lookup(&symlink_changes, path); -+ if (!ent) -+ return 0; -+ return (uintptr_t)ent->util; -+} -+ -+static void prepare_symlink_changes(struct patch *patch) -+{ -+ for ( ; patch; patch = patch->next) { -+ if ((patch->old_name && S_ISLNK(patch->old_mode)) && -+ (patch->is_rename || patch->is_delete)) -+ /* the symlink at patch->old_name is removed */ -+ register_symlink_changes(patch->old_name, SYMLINK_GOES_AWAY); -+ -+ if (patch->new_name && S_ISLNK(patch->new_mode)) -+ /* the symlink at patch->new_name is created or remains */ -+ register_symlink_changes(patch->new_name, SYMLINK_IN_RESULT); -+ } -+} -+ -+static int path_is_beyond_symlink_1(struct strbuf *name) -+{ -+ do { -+ unsigned int change; -+ -+ while (--name->len && name->buf[name->len] != '/') -+ ; /* scan backwards */ -+ if (!name->len) -+ break; -+ name->buf[name->len] = '\0'; -+ change = check_symlink_changes(name->buf); -+ if (change & SYMLINK_IN_RESULT) -+ return 1; -+ if (change & SYMLINK_GOES_AWAY) -+ /* -+ * This cannot be "return 0", because we may -+ * see a new one created at a higher level. -+ */ -+ continue; -+ -+ /* otherwise, check the preimage */ -+ if (check_index) { -+ struct cache_entry *ce; -+ -+ ce = cache_name_exists(name->buf, name->len, ignore_case); -+ if (ce && S_ISLNK(ce->ce_mode)) -+ return 1; -+ } else { -+ struct stat st; -+ if (!lstat(name->buf, &st) && S_ISLNK(st.st_mode)) -+ return 1; -+ } -+ } while (1); -+ return 0; -+} -+ -+static int path_is_beyond_symlink(const char *name_) -+{ -+ int ret; -+ struct strbuf name = STRBUF_INIT; -+ -+ assert(*name_ != '\0'); -+ strbuf_addstr(&name, name_); -+ ret = path_is_beyond_symlink_1(&name); -+ strbuf_release(&name); -+ -+ return ret; -+} -+ -+static void die_on_unsafe_path(struct patch *patch) -+{ -+ const char *old_name = NULL; -+ const char *new_name = NULL; -+ if (patch->is_delete) -+ old_name = patch->old_name; -+ else if (!patch->is_new && !patch->is_copy) -+ old_name = patch->old_name; -+ if (!patch->is_delete) -+ new_name = patch->new_name; -+ -+ if (old_name && !verify_path(old_name, patch->old_mode)) -+ die(_("invalid path '%s'"), old_name); -+ if (new_name && !verify_path(new_name, patch->new_mode)) -+ die(_("invalid path '%s'"), new_name); -+} -+ - /* - * Check and apply the patch in-core; leave the result in patch->result - * for the caller to write it out to the final destination. -@@ -3569,6 +3687,22 @@ static int check_patch(struct patch *patch) - } - } - -+ if (!unsafe_paths) -+ die_on_unsafe_path(patch); -+ -+ /* -+ * An attempt to read from or delete a path that is beyond a -+ * symbolic link will be prevented by load_patch_target() that -+ * is called at the beginning of apply_data() so we do not -+ * have to worry about a patch marked with "is_delete" bit -+ * here. We however need to make sure that the patch result -+ * is not deposited to a path that is beyond a symbolic link -+ * here. -+ */ -+ if (!patch->is_delete && path_is_beyond_symlink(patch->new_name)) -+ return error(_("affected file '%s' is beyond a symbolic link"), -+ patch->new_name); -+ - if (apply_data(patch, &st, ce) < 0) - return error(_("%s: patch does not apply"), name); - patch->rejected = 0; -@@ -3579,6 +3713,7 @@ static int check_patch_list(struct patch *patch) - { - int err = 0; - -+ prepare_symlink_changes(patch); - prepare_fn_table(patch); - while (patch) { - if (apply_verbosely) -@@ -4378,6 +4513,8 @@ int cmd_apply(int argc, const char **argv, const char *prefix_) - N_("make sure the patch is applicable to the current index")), - OPT_BOOLEAN(0, "cached", &cached, - N_("apply a patch without touching the working tree")), -+ OPT_BOOL(0, "unsafe-paths", &unsafe_paths, -+ N_("accept a patch that touches outside the working area")), - OPT_BOOLEAN(0, "apply", &force_apply, - N_("also apply the patch (use with --stat/--summary/--check)")), - OPT_BOOL('3', "3way", &threeway, -@@ -4450,6 +4587,9 @@ int cmd_apply(int argc, const char **argv, const char *prefix_) - die(_("--cached outside a repository")); - check_index = 1; - } -+ if (check_index) -+ unsafe_paths = 0; -+ - for (i = 0; i < argc; i++) { - const char *arg = argv[i]; - int fd; -diff --git a/builtin/config.c b/builtin/config.c -index 19ffcaf..000d27c 100644 ---- a/builtin/config.c -+++ b/builtin/config.c -@@ -21,6 +21,7 @@ static char term = '\n'; - - static int use_global_config, use_system_config, use_local_config; - static const char *given_config_file; -+static const char *given_config_blob; - static int actions, types; - static const char *get_color_slot, *get_colorbool_slot; - static int end_null; -@@ -53,6 +54,7 @@ static struct option builtin_config_options[] = { - OPT_BOOLEAN(0, "system", &use_system_config, N_("use system config file")), - OPT_BOOLEAN(0, "local", &use_local_config, N_("use repository config file")), - OPT_STRING('f', "file", &given_config_file, N_("file"), N_("use given config file")), -+ OPT_STRING(0, "blob", &given_config_blob, N_("blob-id"), N_("read config from given blob object")), - OPT_GROUP(N_("Action")), - OPT_BIT(0, "get", &actions, N_("get value: name [value-regex]"), ACTION_GET), - OPT_BIT(0, "get-all", &actions, N_("get all values: key [value-regex]"), ACTION_GET_ALL), -@@ -218,7 +220,8 @@ static int get_value(const char *key_, const char *regex_) - } - - git_config_with_options(collect_config, &values, -- given_config_file, respect_includes); -+ given_config_file, given_config_blob, -+ respect_includes); - - ret = !values.nr; - -@@ -302,7 +305,8 @@ static void get_color(const char *def_color) - get_color_found = 0; - parsed_color[0] = '\0'; - git_config_with_options(git_get_color_config, NULL, -- given_config_file, respect_includes); -+ given_config_file, given_config_blob, -+ respect_includes); - - if (!get_color_found && def_color) - color_parse(def_color, "command line", parsed_color); -@@ -330,7 +334,8 @@ static int get_colorbool(int print) - get_colorbool_found = -1; - get_diff_color_found = -1; - git_config_with_options(git_get_colorbool_config, NULL, -- given_config_file, respect_includes); -+ given_config_file, given_config_blob, -+ respect_includes); - - if (get_colorbool_found < 0) { - if (!strcmp(get_colorbool_slot, "color.diff")) -@@ -348,6 +353,12 @@ static int get_colorbool(int print) - return get_colorbool_found ? 0 : 1; - } - -+static void check_blob_write(void) -+{ -+ if (given_config_blob) -+ die("writing config blobs is not supported"); -+} -+ - int cmd_config(int argc, const char **argv, const char *prefix) - { - int nongit = !startup_info->have_repository; -@@ -359,7 +370,8 @@ int cmd_config(int argc, const char **argv, const char *prefix) - builtin_config_usage, - PARSE_OPT_STOP_AT_NON_OPTION); - -- if (use_global_config + use_system_config + use_local_config + !!given_config_file > 1) { -+ if (use_global_config + use_system_config + use_local_config + -+ !!given_config_file + !!given_config_blob > 1) { - error("only one config file at a time."); - usage_with_options(builtin_config_usage, builtin_config_options); - } -@@ -438,6 +450,7 @@ int cmd_config(int argc, const char **argv, const char *prefix) - check_argc(argc, 0, 0); - if (git_config_with_options(show_all_config, NULL, - given_config_file, -+ given_config_blob, - respect_includes) < 0) { - if (given_config_file) - die_errno("unable to read config file '%s'", -@@ -450,6 +463,8 @@ int cmd_config(int argc, const char **argv, const char *prefix) - check_argc(argc, 0, 0); - if (!given_config_file && nongit) - die("not in a git directory"); -+ if (given_config_blob) -+ die("editing blobs is not supported"); - git_config(git_default_config, NULL); - launch_editor(given_config_file ? - given_config_file : git_path("config"), -@@ -457,6 +472,7 @@ int cmd_config(int argc, const char **argv, const char *prefix) - } - else if (actions == ACTION_SET) { - int ret; -+ check_blob_write(); - check_argc(argc, 2, 2); - value = normalize_value(argv[0], argv[1]); - ret = git_config_set_in_file(given_config_file, argv[0], value); -@@ -466,18 +482,21 @@ int cmd_config(int argc, const char **argv, const char *prefix) - return ret; - } - else if (actions == ACTION_SET_ALL) { -+ check_blob_write(); - check_argc(argc, 2, 3); - value = normalize_value(argv[0], argv[1]); - return git_config_set_multivar_in_file(given_config_file, - argv[0], value, argv[2], 0); - } - else if (actions == ACTION_ADD) { -+ check_blob_write(); - check_argc(argc, 2, 2); - value = normalize_value(argv[0], argv[1]); - return git_config_set_multivar_in_file(given_config_file, - argv[0], value, "^$", 0); - } - else if (actions == ACTION_REPLACE_ALL) { -+ check_blob_write(); - check_argc(argc, 2, 3); - value = normalize_value(argv[0], argv[1]); - return git_config_set_multivar_in_file(given_config_file, -@@ -500,6 +519,7 @@ int cmd_config(int argc, const char **argv, const char *prefix) - return get_value(argv[0], argv[1]); - } - else if (actions == ACTION_UNSET) { -+ check_blob_write(); - check_argc(argc, 1, 2); - if (argc == 2) - return git_config_set_multivar_in_file(given_config_file, -@@ -509,12 +529,14 @@ int cmd_config(int argc, const char **argv, const char *prefix) - argv[0], NULL); - } - else if (actions == ACTION_UNSET_ALL) { -+ check_blob_write(); - check_argc(argc, 1, 2); - return git_config_set_multivar_in_file(given_config_file, - argv[0], NULL, argv[1], 1); - } - else if (actions == ACTION_RENAME_SECTION) { - int ret; -+ check_blob_write(); - check_argc(argc, 2, 2); - ret = git_config_rename_section_in_file(given_config_file, - argv[0], argv[1]); -@@ -525,6 +547,7 @@ int cmd_config(int argc, const char **argv, const char *prefix) - } - else if (actions == ACTION_REMOVE_SECTION) { - int ret; -+ check_blob_write(); - check_argc(argc, 1, 1); - ret = git_config_rename_section_in_file(given_config_file, - argv[0], NULL); -diff --git a/builtin/fsck.c b/builtin/fsck.c -index bb9a2cd..b59f956 100644 ---- a/builtin/fsck.c -+++ b/builtin/fsck.c -@@ -35,14 +35,6 @@ static int show_dangling = 1; - #define ERROR_REACHABLE 02 - #define ERROR_PACK 04 - --#ifdef NO_D_INO_IN_DIRENT --#define SORT_DIRENT 0 --#define DIRENT_SORT_HINT(de) 0 --#else --#define SORT_DIRENT 1 --#define DIRENT_SORT_HINT(de) ((de)->d_ino) --#endif -- - static void objreport(struct object *obj, const char *severity, - const char *err, va_list params) - { -@@ -288,7 +280,7 @@ static void check_connectivity(void) - } - } - --static int fsck_obj(struct object *obj) -+static int fsck_obj(struct object *obj, void *buffer, unsigned long size) - { - if (obj->flags & SEEN) - return 0; -@@ -300,7 +292,7 @@ static int fsck_obj(struct object *obj) - - if (fsck_walk(obj, mark_used, NULL)) - objerror(obj, "broken links"); -- if (fsck_object(obj, check_strict, fsck_error_func)) -+ if (fsck_object(obj, buffer, size, check_strict, fsck_error_func)) - return -1; - - if (obj->type == OBJ_TREE) { -@@ -332,17 +324,6 @@ static int fsck_obj(struct object *obj) - return 0; - } - --static int fsck_sha1(const unsigned char *sha1) --{ -- struct object *obj = parse_object(sha1); -- if (!obj) { -- errors_found |= ERROR_OBJECT; -- return error("%s: object corrupt or missing", -- sha1_to_hex(sha1)); -- } -- return fsck_obj(obj); --} -- - static int fsck_obj_buffer(const unsigned char *sha1, enum object_type type, - unsigned long size, void *buffer, int *eaten) - { -@@ -352,86 +333,69 @@ static int fsck_obj_buffer(const unsigned char *sha1, enum object_type type, - errors_found |= ERROR_OBJECT; - return error("%s: object corrupt or missing", sha1_to_hex(sha1)); - } -- return fsck_obj(obj); -+ return fsck_obj(obj, buffer, size); - } - --/* -- * This is the sorting chunk size: make it reasonably -- * big so that we can sort well.. -- */ --#define MAX_SHA1_ENTRIES (1024) -- --struct sha1_entry { -- unsigned long ino; -- unsigned char sha1[20]; --}; -- --static struct { -- unsigned long nr; -- struct sha1_entry *entry[MAX_SHA1_ENTRIES]; --} sha1_list; -- --static int ino_compare(const void *_a, const void *_b) -+static inline int is_loose_object_file(struct dirent *de, -+ char *name, unsigned char *sha1) - { -- const struct sha1_entry *a = _a, *b = _b; -- unsigned long ino1 = a->ino, ino2 = b->ino; -- return ino1 < ino2 ? -1 : ino1 > ino2 ? 1 : 0; -+ if (strlen(de->d_name) != 38) -+ return 0; -+ memcpy(name + 2, de->d_name, 39); -+ return !get_sha1_hex(name, sha1); - } - --static void fsck_sha1_list(void) -+static void fsck_loose(const unsigned char *sha1, const char *path) - { -- int i, nr = sha1_list.nr; -- -- if (SORT_DIRENT) -- qsort(sha1_list.entry, nr, -- sizeof(struct sha1_entry *), ino_compare); -- for (i = 0; i < nr; i++) { -- struct sha1_entry *entry = sha1_list.entry[i]; -- unsigned char *sha1 = entry->sha1; -- -- sha1_list.entry[i] = NULL; -- fsck_sha1(sha1); -- free(entry); -+ struct object *obj; -+ enum object_type type; -+ unsigned long size; -+ void *contents; -+ int eaten; -+ -+ if (read_loose_object(path, sha1, &type, &size, &contents) < 0) { -+ errors_found |= ERROR_OBJECT; -+ error("%s: object corrupt or missing: %s", -+ sha1_to_hex(sha1), path); -+ return; /* keep checking other objects */ - } -- sha1_list.nr = 0; --} - --static void add_sha1_list(unsigned char *sha1, unsigned long ino) --{ -- struct sha1_entry *entry = xmalloc(sizeof(*entry)); -- int nr; -- -- entry->ino = ino; -- hashcpy(entry->sha1, sha1); -- nr = sha1_list.nr; -- if (nr == MAX_SHA1_ENTRIES) { -- fsck_sha1_list(); -- nr = 0; -+ if (!contents && type != OBJ_BLOB) -+ die("BUG: read_loose_object streamed a non-blob"); -+ -+ obj = parse_object_buffer(sha1, type, size, contents, &eaten); -+ -+ if (!obj) { -+ errors_found |= ERROR_OBJECT; -+ error("%s: object could not be parsed: %s", -+ sha1_to_hex(sha1), path); -+ if (!eaten) -+ free(contents); -+ return; /* keep checking other objects */ - } -- sha1_list.entry[nr] = entry; -- sha1_list.nr = ++nr; --} - --static inline int is_loose_object_file(struct dirent *de, -- char *name, unsigned char *sha1) --{ -- if (strlen(de->d_name) != 38) -- return 0; -- memcpy(name + 2, de->d_name, 39); -- return !get_sha1_hex(name, sha1); -+ if (fsck_obj(obj, contents, size)) -+ errors_found |= ERROR_OBJECT; -+ -+ if (!eaten) -+ free(contents); - } - --static void fsck_dir(int i, char *path) -+static void fsck_dir(int i, struct strbuf *path) - { -- DIR *dir = opendir(path); -+ DIR *dir = opendir(path->buf); - struct dirent *de; - char name[100]; -+ size_t dirlen; - - if (!dir) - return; - - if (verbose) -- fprintf(stderr, "Checking directory %s\n", path); -+ fprintf(stderr, "Checking directory %s\n", path->buf); -+ -+ strbuf_addch(path, '/'); -+ dirlen = path->len; - - sprintf(name, "%02x", i); - while ((de = readdir(dir)) != NULL) { -@@ -439,15 +403,20 @@ static void fsck_dir(int i, char *path) - - if (is_dot_or_dotdot(de->d_name)) - continue; -+ -+ strbuf_setlen(path, dirlen); -+ strbuf_addstr(path, de->d_name); -+ - if (is_loose_object_file(de, name, sha1)) { -- add_sha1_list(sha1, DIRENT_SORT_HINT(de)); -+ fsck_loose(sha1, path->buf); - continue; - } - if (!prefixcmp(de->d_name, "tmp_obj_")) - continue; -- fprintf(stderr, "bad sha1 file: %s/%s\n", path, de->d_name); -+ fprintf(stderr, "bad sha1 file: %s\n", path->buf); - } - closedir(dir); -+ strbuf_setlen(path, dirlen-1); - } - - static int default_refs; -@@ -533,24 +502,28 @@ static void get_default_heads(void) - } - } - --static void fsck_object_dir(const char *path) -+static void fsck_object_dir(struct strbuf *path) - { - int i; - struct progress *progress = NULL; -+ size_t dirlen; - - if (verbose) - fprintf(stderr, "Checking object directory\n"); - -+ strbuf_addch(path, '/'); -+ dirlen = path->len; -+ - if (show_progress) - progress = start_progress("Checking object directories", 256); - for (i = 0; i < 256; i++) { -- static char dir[4096]; -- sprintf(dir, "%s/%02x", path, i); -- fsck_dir(i, dir); -+ strbuf_setlen(path, dirlen); -+ strbuf_addf(path, "%02x", i); -+ fsck_dir(i, path); - display_progress(progress, i+1); - } - stop_progress(&progress); -- fsck_sha1_list(); -+ strbuf_setlen(path, dirlen - 1); - } - - static int fsck_head_link(void) -@@ -629,6 +602,7 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) - { - int i, heads; - struct alternate_object_database *alt; -+ struct strbuf dir = STRBUF_INIT; - - errors_found = 0; - read_replace_refs = 0; -@@ -646,15 +620,14 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) - } - - fsck_head_link(); -- fsck_object_dir(get_object_directory()); -+ strbuf_addstr(&dir, get_object_directory()); -+ fsck_object_dir(&dir); - - prepare_alt_odb(); - for (alt = alt_odb_list; alt; alt = alt->next) { -- char namebuf[PATH_MAX]; -- int namelen = alt->name - alt->base; -- memcpy(namebuf, alt->base, namelen); -- namebuf[namelen - 1] = 0; -- fsck_object_dir(namebuf); -+ strbuf_reset(&dir); -+ strbuf_add(&dir, alt->base, alt->name - alt->base - 1); -+ fsck_object_dir(&dir); - } - - if (check_full) { -@@ -681,6 +654,9 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) - count += p->num_objects; - } - stop_progress(&progress); -+ -+ if (fsck_finish(fsck_error_func)) -+ errors_found |= ERROR_OBJECT; - } - - heads = 0; -@@ -734,5 +710,6 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) - } - - check_connectivity(); -+ strbuf_release(&dir); - return errors_found; - } -diff --git a/builtin/index-pack.c b/builtin/index-pack.c -index 79dfe47..3cc2bf6 100644 ---- a/builtin/index-pack.c -+++ b/builtin/index-pack.c -@@ -742,6 +742,8 @@ static void sha1_object(const void *data, struct object_entry *obj_entry, - blob->object.flags |= FLAG_CHECKED; - else - die(_("invalid blob object %s"), sha1_to_hex(sha1)); -+ if (fsck_object(&blob->object, (void *)data, size, 1, fsck_error_function)) -+ die(_("fsck error in packed object")); - } else { - struct object *obj; - int eaten; -@@ -757,8 +759,9 @@ static void sha1_object(const void *data, struct object_entry *obj_entry, - obj = parse_object_buffer(sha1, type, size, buf, &eaten); - if (!obj) - die(_("invalid %s"), typename(type)); -- if (fsck_object(obj, 1, fsck_error_function)) -- die(_("Error in object")); -+ if (fsck_object(obj, buf, size, 1, -+ fsck_error_function)) -+ die(_("fsck error in packed object")); - if (fsck_walk(obj, mark_link, NULL)) - die(_("Not all child objects of %s are reachable"), sha1_to_hex(obj->sha1)); - -@@ -1320,6 +1323,8 @@ static void final(const char *final_pack_name, const char *curr_pack_name, - } else - chmod(final_index_name, 0444); - -+ add_packed_git(final_index_name, strlen(final_index_name), 0); -+ - if (!from_stdin) { - printf("%s\n", sha1_to_hex(sha1)); - } else { -@@ -1643,6 +1648,10 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) - pack_sha1); - else - close(input_fd); -+ -+ if (fsck_finish(fsck_error_function)) -+ die(_("fsck error in pack objects")); -+ - free(objects); - free(index_name_buf); - free(keep_name_buf); -diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c -new file mode 100644 -index 0000000..cc79d05 ---- /dev/null -+++ b/builtin/submodule--helper.c -@@ -0,0 +1,35 @@ -+#include "builtin.h" -+#include "submodule.h" -+#include "strbuf.h" -+ -+/* -+ * Exit non-zero if any of the submodule names given on the command line is -+ * invalid. If no names are given, filter stdin to print only valid names -+ * (which is primarily intended for testing). -+ */ -+static int check_name(int argc, const char **argv, const char *prefix) -+{ -+ if (argc > 1) { -+ while (*++argv) { -+ if (check_submodule_name(*argv) < 0) -+ return 1; -+ } -+ } else { -+ struct strbuf buf = STRBUF_INIT; -+ while (strbuf_getline(&buf, stdin, '\n') != EOF) { -+ if (!check_submodule_name(buf.buf)) -+ printf("%s\n", buf.buf); -+ } -+ strbuf_release(&buf); -+ } -+ return 0; -+} -+ -+int cmd_submodule__helper(int argc, const char **argv, const char *prefix) -+{ -+ if (argc < 2) -+ usage("git submodule--helper "); -+ if (!strcmp(argv[1], "check-name")) -+ return check_name(argc - 1, argv + 1, prefix); -+ die(_("'%s' is not a valid submodule--helper subcommand"), argv[1]); -+} -diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c -index 2217d7b..f5a44ba 100644 ---- a/builtin/unpack-objects.c -+++ b/builtin/unpack-objects.c -@@ -164,10 +164,10 @@ static unsigned nr_objects; - * Called only from check_object() after it verified this object - * is Ok. - */ --static void write_cached_object(struct object *obj) -+static void write_cached_object(struct object *obj, struct obj_buffer *obj_buf) - { - unsigned char sha1[20]; -- struct obj_buffer *obj_buf = lookup_object_buffer(obj); -+ - if (write_sha1_file(obj_buf->buffer, obj_buf->size, typename(obj->type), sha1) < 0) - die("failed to write object %s", sha1_to_hex(obj->sha1)); - obj->flags |= FLAG_WRITTEN; -@@ -180,6 +180,8 @@ static void write_cached_object(struct object *obj) - */ - static int check_object(struct object *obj, int type, void *data) - { -+ struct obj_buffer *obj_buf; -+ - if (!obj) - return 1; - -@@ -198,11 +200,15 @@ static int check_object(struct object *obj, int type, void *data) - return 0; - } - -- if (fsck_object(obj, 1, fsck_error_function)) -- die("Error in object"); -+ obj_buf = lookup_object_buffer(obj); -+ if (!obj_buf) -+ die("Whoops! Cannot find object '%s'", sha1_to_hex(obj->sha1)); -+ if (fsck_object(obj, obj_buf->buffer, obj_buf->size, 1, -+ fsck_error_function)) -+ die("fsck error in packed object"); - if (fsck_walk(obj, check_object, NULL)) - die("Error on reachable objects of %s", sha1_to_hex(obj->sha1)); -- write_cached_object(obj); -+ write_cached_object(obj, obj_buf); - return 0; - } - -@@ -548,8 +554,11 @@ int cmd_unpack_objects(int argc, const char **argv, const char *prefix) - unpack_all(); - git_SHA1_Update(&ctx, buffer, offset); - git_SHA1_Final(sha1, &ctx); -- if (strict) -+ if (strict) { - write_rest(); -+ if (fsck_finish(fsck_error_function)) -+ die(_("fsck error in pack objects")); -+ } - if (hashcmp(fill(20), sha1)) - die("final sha1 did not match"); - use(20); -diff --git a/builtin/update-index.c b/builtin/update-index.c -index 5c7762e..bd3715e 100644 ---- a/builtin/update-index.c -+++ b/builtin/update-index.c -@@ -179,10 +179,9 @@ static int process_directory(const char *path, int len, struct stat *st) - return error("%s: is a directory - add files inside instead", path); - } - --static int process_path(const char *path) -+static int process_path(const char *path, struct stat *st, int stat_errno) - { - int pos, len; -- struct stat st; - struct cache_entry *ce; - - len = strlen(path); -@@ -206,13 +205,13 @@ static int process_path(const char *path) - * First things first: get the stat information, to decide - * what to do about the pathname! - */ -- if (lstat(path, &st) < 0) -- return process_lstat_error(path, errno); -+ if (stat_errno) -+ return process_lstat_error(path, stat_errno); - -- if (S_ISDIR(st.st_mode)) -- return process_directory(path, len, &st); -+ if (S_ISDIR(st->st_mode)) -+ return process_directory(path, len, st); - -- return add_one_path(ce, path, len, &st); -+ return add_one_path(ce, path, len, st); - } - - static int add_cacheinfo(unsigned int mode, const unsigned char *sha1, -@@ -221,7 +220,7 @@ static int add_cacheinfo(unsigned int mode, const unsigned char *sha1, - int size, len, option; - struct cache_entry *ce; - -- if (!verify_path(path)) -+ if (!verify_path(path, mode)) - return error("Invalid path '%s'", path); - - len = strlen(path); -@@ -276,7 +275,17 @@ static void chmod_path(int flip, const char *path) - static void update_one(const char *path, const char *prefix, int prefix_length) - { - const char *p = prefix_path(prefix, prefix_length, path); -- if (!verify_path(p)) { -+ int stat_errno = 0; -+ struct stat st; -+ -+ if (mark_valid_only || mark_skip_worktree_only || force_remove) -+ st.st_mode = 0; -+ else if (lstat(p, &st) < 0) { -+ st.st_mode = 0; -+ stat_errno = errno; -+ } /* else stat is valid */ -+ -+ if (!verify_path(p, st.st_mode)) { - fprintf(stderr, "Ignoring path %s\n", path); - goto free_return; - } -@@ -297,7 +306,7 @@ static void update_one(const char *path, const char *prefix, int prefix_length) - report("remove '%s'", path); - goto free_return; - } -- if (process_path(p)) -+ if (process_path(p, &st, stat_errno)) - die("Unable to process path %s", path); - report("add '%s'", path); - free_return: -@@ -367,7 +376,7 @@ static void read_index_info(int line_termination) - path_name = uq.buf; - } - -- if (!verify_path(path_name)) { -+ if (!verify_path(path_name, mode)) { - fprintf(stderr, "Ignoring path %s\n", path_name); - continue; - } -diff --git a/cache.h b/cache.h -index 2ab9ffd..e0dc079 100644 ---- a/cache.h -+++ b/cache.h -@@ -448,7 +448,7 @@ extern int read_index_unmerged(struct index_state *); - extern int write_index(struct index_state *, int newfd); - extern int discard_index(struct index_state *); - extern int unmerged_index(const struct index_state *); --extern int verify_path(const char *path); -+extern int verify_path(const char *path, unsigned mode); - extern struct cache_entry *index_name_exists(struct index_state *istate, const char *name, int namelen, int igncase); - extern int index_name_pos(const struct index_state *, const char *name, int namelen); - #define ADD_CACHE_OK_TO_ADD 1 /* Ok to add */ -@@ -883,6 +883,19 @@ extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref); - extern int interpret_branch_name(const char *str, struct strbuf *); - extern int get_sha1_mb(const char *str, unsigned char *sha1); - -+/* -+ * Open the loose object at path, check its sha1, and return the contents, -+ * type, and size. If the object is a blob, then "contents" may return NULL, -+ * to allow streaming of large blobs. -+ * -+ * Returns 0 on success, negative on error (details may be written to stderr). -+ */ -+int read_loose_object(const char *path, -+ const unsigned char *expected_sha1, -+ enum object_type *type, -+ unsigned long *size, -+ void **contents); -+ - extern int refname_match(const char *abbrev_name, const char *full_name, const char **rules); - extern const char *ref_rev_parse_rules[]; - #define ref_fetch_rules ref_rev_parse_rules -@@ -1150,11 +1163,15 @@ extern int update_server_info(int); - typedef int (*config_fn_t)(const char *, const char *, void *); - extern int git_default_config(const char *, const char *, void *); - extern int git_config_from_file(config_fn_t fn, const char *, void *); -+extern int git_config_from_buf(config_fn_t fn, const char *name, -+ const char *buf, size_t len, void *data); - extern void git_config_push_parameter(const char *text); - extern int git_config_from_parameters(config_fn_t fn, void *data); - extern int git_config(config_fn_t fn, void *); - extern int git_config_with_options(config_fn_t fn, void *, -- const char *filename, int respect_includes); -+ const char *filename, -+ const char *blob_ref, -+ int respect_includes); - extern int git_config_early(config_fn_t fn, void *, const char *repo_config); - extern int git_parse_ulong(const char *, unsigned long *); - extern int git_config_int(const char *, const char *); -diff --git a/config.c b/config.c -index 830ee14..201930f 100644 ---- a/config.c -+++ b/config.c -@@ -10,20 +10,69 @@ - #include "strbuf.h" - #include "quote.h" - --typedef struct config_file { -- struct config_file *prev; -- FILE *f; -+struct config_source { -+ struct config_source *prev; -+ union { -+ FILE *file; -+ struct config_buf { -+ const char *buf; -+ size_t len; -+ size_t pos; -+ } buf; -+ } u; - const char *name; -+ int die_on_error; - int linenr; - int eof; - struct strbuf value; - struct strbuf var; --} config_file; - --static config_file *cf; -+ int (*do_fgetc)(struct config_source *c); -+ int (*do_ungetc)(int c, struct config_source *conf); -+ long (*do_ftell)(struct config_source *c); -+}; -+ -+static struct config_source *cf; - - static int zlib_compression_seen; - -+static int config_file_fgetc(struct config_source *conf) -+{ -+ return fgetc(conf->u.file); -+} -+ -+static int config_file_ungetc(int c, struct config_source *conf) -+{ -+ return ungetc(c, conf->u.file); -+} -+ -+static long config_file_ftell(struct config_source *conf) -+{ -+ return ftell(conf->u.file); -+} -+ -+ -+static int config_buf_fgetc(struct config_source *conf) -+{ -+ if (conf->u.buf.pos < conf->u.buf.len) -+ return conf->u.buf.buf[conf->u.buf.pos++]; -+ -+ return EOF; -+} -+ -+static int config_buf_ungetc(int c, struct config_source *conf) -+{ -+ if (conf->u.buf.pos > 0) -+ return conf->u.buf.buf[--conf->u.buf.pos]; -+ -+ return EOF; -+} -+ -+static long config_buf_ftell(struct config_source *conf) -+{ -+ return conf->u.buf.pos; -+} -+ - #define MAX_INCLUDE_DEPTH 10 - static const char include_depth_advice[] = - "exceeded maximum include depth (%d) while including\n" -@@ -168,27 +217,22 @@ int git_config_from_parameters(config_fn_t fn, void *data) - - static int get_next_char(void) - { -- int c; -- FILE *f; -- -- c = '\n'; -- if (cf && ((f = cf->f) != NULL)) { -- c = fgetc(f); -- if (c == '\r') { -- /* DOS like systems */ -- c = fgetc(f); -- if (c != '\n') { -- ungetc(c, f); -- c = '\r'; -- } -- } -- if (c == '\n') -- cf->linenr++; -- if (c == EOF) { -- cf->eof = 1; -- c = '\n'; -+ int c = cf->do_fgetc(cf); -+ -+ if (c == '\r') { -+ /* DOS like systems */ -+ c = cf->do_fgetc(cf); -+ if (c != '\n') { -+ cf->do_ungetc(c, cf); -+ c = '\r'; - } - } -+ if (c == '\n') -+ cf->linenr++; -+ if (c == EOF) { -+ cf->eof = 1; -+ c = '\n'; -+ } - return c; - } - -@@ -339,7 +383,7 @@ static int get_base_var(struct strbuf *name) - } - } - --static int git_parse_file(config_fn_t fn, void *data) -+static int git_parse_source(config_fn_t fn, void *data) - { - int comment = 0; - int baselen = 0; -@@ -399,7 +443,10 @@ static int git_parse_file(config_fn_t fn, void *data) - if (get_value(fn, data, var) < 0) - break; - } -- die("bad config file line %d in %s", cf->linenr, cf->name); -+ if (cf->die_on_error) -+ die("bad config file line %d in %s", cf->linenr, cf->name); -+ else -+ return error("bad config file line %d in %s", cf->linenr, cf->name); - } - - static int parse_unit_factor(const char *end, uintmax_t *val) -@@ -896,6 +943,33 @@ int git_default_config(const char *var, const char *value, void *dummy) - return 0; - } - -+/* -+ * All source specific fields in the union, die_on_error, name and the callbacks -+ * fgetc, ungetc, ftell of top need to be initialized before calling -+ * this function. -+ */ -+static int do_config_from(struct config_source *top, config_fn_t fn, void *data) -+{ -+ int ret; -+ -+ /* push config-file parsing state stack */ -+ top->prev = cf; -+ top->linenr = 1; -+ top->eof = 0; -+ strbuf_init(&top->value, 1024); -+ strbuf_init(&top->var, 1024); -+ cf = top; -+ -+ ret = git_parse_source(fn, data); -+ -+ /* pop config-file parsing state stack */ -+ strbuf_release(&top->value); -+ strbuf_release(&top->var); -+ cf = top->prev; -+ -+ return ret; -+} -+ - int git_config_from_file(config_fn_t fn, const char *filename, void *data) - { - int ret; -@@ -903,30 +977,74 @@ int git_config_from_file(config_fn_t fn, const char *filename, void *data) - - ret = -1; - if (f) { -- config_file top; -+ struct config_source top; - -- /* push config-file parsing state stack */ -- top.prev = cf; -- top.f = f; -+ top.u.file = f; - top.name = filename; -- top.linenr = 1; -- top.eof = 0; -- strbuf_init(&top.value, 1024); -- strbuf_init(&top.var, 1024); -- cf = ⊤ -- -- ret = git_parse_file(fn, data); -+ top.die_on_error = 1; -+ top.do_fgetc = config_file_fgetc; -+ top.do_ungetc = config_file_ungetc; -+ top.do_ftell = config_file_ftell; - -- /* pop config-file parsing state stack */ -- strbuf_release(&top.value); -- strbuf_release(&top.var); -- cf = top.prev; -+ ret = do_config_from(&top, fn, data); - - fclose(f); - } - return ret; - } - -+int git_config_from_buf(config_fn_t fn, const char *name, const char *buf, -+ size_t len, void *data) -+{ -+ struct config_source top; -+ -+ top.u.buf.buf = buf; -+ top.u.buf.len = len; -+ top.u.buf.pos = 0; -+ top.name = name; -+ top.die_on_error = 0; -+ top.do_fgetc = config_buf_fgetc; -+ top.do_ungetc = config_buf_ungetc; -+ top.do_ftell = config_buf_ftell; -+ -+ return do_config_from(&top, fn, data); -+} -+ -+static int git_config_from_blob_sha1(config_fn_t fn, -+ const char *name, -+ const unsigned char *sha1, -+ void *data) -+{ -+ enum object_type type; -+ char *buf; -+ unsigned long size; -+ int ret; -+ -+ buf = read_sha1_file(sha1, &type, &size); -+ if (!buf) -+ return error("unable to load config blob object '%s'", name); -+ if (type != OBJ_BLOB) { -+ free(buf); -+ return error("reference '%s' does not point to a blob", name); -+ } -+ -+ ret = git_config_from_buf(fn, name, buf, size, data); -+ free(buf); -+ -+ return ret; -+} -+ -+static int git_config_from_blob_ref(config_fn_t fn, -+ const char *name, -+ void *data) -+{ -+ unsigned char sha1[20]; -+ -+ if (get_sha1(name, sha1) < 0) -+ return error("unable to resolve config blob '%s'", name); -+ return git_config_from_blob_sha1(fn, name, sha1, data); -+} -+ - const char *git_etc_gitconfig(void) - { - static const char *system_wide; -@@ -992,7 +1110,9 @@ int git_config_early(config_fn_t fn, void *data, const char *repo_config) - } - - int git_config_with_options(config_fn_t fn, void *data, -- const char *filename, int respect_includes) -+ const char *filename, -+ const char *blob_ref, -+ int respect_includes) - { - char *repo_config = NULL; - int ret; -@@ -1011,6 +1131,8 @@ int git_config_with_options(config_fn_t fn, void *data, - */ - if (filename) - return git_config_from_file(fn, filename, data); -+ else if (blob_ref) -+ return git_config_from_blob_ref(fn, blob_ref, data); - - repo_config = git_pathdup("config"); - ret = git_config_early(fn, data, repo_config); -@@ -1021,7 +1143,7 @@ int git_config_with_options(config_fn_t fn, void *data, - - int git_config(config_fn_t fn, void *data) - { -- return git_config_with_options(fn, data, NULL, 1); -+ return git_config_with_options(fn, data, NULL, NULL, 1); - } - - /* -@@ -1053,7 +1175,6 @@ static int store_aux(const char *key, const char *value, void *cb) - { - const char *ep; - size_t section_len; -- FILE *f = cf->f; - - switch (store.state) { - case KEY_SEEN: -@@ -1065,7 +1186,7 @@ static int store_aux(const char *key, const char *value, void *cb) - return 1; - } - -- store.offset[store.seen] = ftell(f); -+ store.offset[store.seen] = cf->do_ftell(cf); - store.seen++; - } - break; -@@ -1092,19 +1213,19 @@ static int store_aux(const char *key, const char *value, void *cb) - * Do not increment matches: this is no match, but we - * just made sure we are in the desired section. - */ -- store.offset[store.seen] = ftell(f); -+ store.offset[store.seen] = cf->do_ftell(cf); - /* fallthru */ - case SECTION_END_SEEN: - case START: - if (matches(key, value)) { -- store.offset[store.seen] = ftell(f); -+ store.offset[store.seen] = cf->do_ftell(cf); - store.state = KEY_SEEN; - store.seen++; - } else { - if (strrchr(key, '.') - key == store.baselen && - !strncmp(key, store.key, store.baselen)) { - store.state = SECTION_SEEN; -- store.offset[store.seen] = ftell(f); -+ store.offset[store.seen] = cf->do_ftell(cf); - } - } - } -diff --git a/fsck.c b/fsck.c -index 99c0497..3c090bd 100644 ---- a/fsck.c -+++ b/fsck.c -@@ -6,6 +6,42 @@ - #include "commit.h" - #include "tag.h" - #include "fsck.h" -+#include "hashmap.h" -+#include "submodule.h" -+ -+struct oidhash_entry { -+ struct hashmap_entry ent; -+ unsigned char sha1[20]; -+}; -+ -+static int oidhash_hashcmp(const void *va, const void *vb, -+ const void *vkey) -+{ -+ const struct oidhash_entry *a = va, *b = vb; -+ const unsigned char *key = vkey; -+ return hashcmp(a->sha1, key ? key : b->sha1); -+} -+ -+static struct hashmap gitmodules_found; -+static struct hashmap gitmodules_done; -+ -+static void oidhash_insert(struct hashmap *h, const unsigned char *sha1) -+{ -+ struct oidhash_entry *e; -+ -+ if (!h->tablesize) -+ hashmap_init(h, oidhash_hashcmp, 0); -+ e = xmalloc(sizeof(*e)); -+ hashmap_entry_init(&e->ent, sha1hash(sha1)); -+ hashcpy(e->sha1, sha1); -+ hashmap_add(h, e); -+} -+ -+static int oidhash_contains(struct hashmap *h, const unsigned char *sha1) -+{ -+ return h->tablesize && -+ !!hashmap_get_from_hash(h, sha1hash(sha1), sha1); -+} - - static int fsck_walk_tree(struct tree *tree, fsck_walk_func walk, void *data) - { -@@ -138,7 +174,7 @@ static int verify_ordered(unsigned mode1, const char *name1, unsigned mode2, con - - static int fsck_tree(struct tree *item, int strict, fsck_error error_func) - { -- int retval; -+ int retval = 0; - int has_null_sha1 = 0; - int has_full_path = 0; - int has_empty_name = 0; -@@ -178,6 +214,16 @@ static int fsck_tree(struct tree *item, int strict, fsck_error error_func) - if (!strcmp(name, ".git")) - has_dotgit = 1; - has_zero_pad |= *(char *)desc.buffer == '0'; -+ -+ if (!strcmp(name, ".gitmodules")) { -+ if (!S_ISLNK(mode)) -+ oidhash_insert(&gitmodules_found, sha1); -+ else -+ retval += error_func(&item->object, -+ FSCK_ERROR, -+ ".gitmodules is a symbolic link"); -+ } -+ - update_tree_entry(&desc); - - switch (mode) { -@@ -219,7 +265,6 @@ static int fsck_tree(struct tree *item, int strict, fsck_error error_func) - o_name = name; - } - -- retval = 0; - if (has_null_sha1) - retval += error_func(&item->object, FSCK_WARN, "contains entries pointing to null sha1"); - if (has_full_path) -@@ -243,6 +288,26 @@ static int fsck_tree(struct tree *item, int strict, fsck_error error_func) - return retval; - } - -+static int require_end_of_header(const void *data, unsigned long size, -+ struct object *obj, fsck_error error_func) -+{ -+ const char *buffer = (const char *)data; -+ unsigned long i; -+ -+ for (i = 0; i < size; i++) { -+ switch (buffer[i]) { -+ case '\0': -+ return error_func(obj, FSCK_ERROR, -+ "unterminated header: NUL at offset %d", i); -+ case '\n': -+ if (i + 1 < size && buffer[i + 1] == '\n') -+ return 0; -+ } -+ } -+ -+ return error_func(obj, FSCK_ERROR, "unterminated header"); -+} -+ - static int fsck_ident(char **ident, struct object *obj, fsck_error error_func) - { - if (**ident == '<') -@@ -279,9 +344,10 @@ static int fsck_ident(char **ident, struct object *obj, fsck_error error_func) - return 0; - } - --static int fsck_commit(struct commit *commit, fsck_error error_func) -+static int fsck_commit(struct commit *commit, char *data, -+ unsigned long size, fsck_error error_func) - { -- char *buffer = commit->buffer; -+ char *buffer = data ? data : commit->buffer; - unsigned char tree_sha1[20], sha1[20]; - struct commit_graft *graft; - int parents = 0; -@@ -290,6 +356,9 @@ static int fsck_commit(struct commit *commit, fsck_error error_func) - if (commit->date == ULONG_MAX) - return error_func(&commit->object, FSCK_ERROR, "invalid author/committer line"); - -+ if (require_end_of_header(buffer, size, &commit->object, error_func)) -+ return -1; -+ - if (memcmp(buffer, "tree ", 5)) - return error_func(&commit->object, FSCK_ERROR, "invalid format - expected 'tree' line"); - if (get_sha1_hex(buffer+5, tree_sha1) || buffer[45] != '\n') -@@ -340,7 +409,8 @@ static int fsck_commit(struct commit *commit, fsck_error error_func) - return 0; - } - --static int fsck_tag(struct tag *tag, fsck_error error_func) -+static int fsck_tag(struct tag *tag, const char *data, -+ unsigned long size, fsck_error error_func) - { - struct object *tagged = tag->tagged; - -@@ -349,19 +419,80 @@ static int fsck_tag(struct tag *tag, fsck_error error_func) - return 0; - } - --int fsck_object(struct object *obj, int strict, fsck_error error_func) -+struct fsck_gitmodules_data { -+ struct object *obj; -+ fsck_error error_func; -+ int ret; -+}; -+ -+static int fsck_gitmodules_fn(const char *var, const char *value, void *vdata) -+{ -+ struct fsck_gitmodules_data *data = vdata; -+ const char *subsection, *key; -+ int subsection_len; -+ char *name; -+ -+ if (parse_config_key(var, "submodule", &subsection, &subsection_len, &key) < 0 || -+ !subsection) -+ return 0; -+ -+ name = xmemdupz(subsection, subsection_len); -+ if (check_submodule_name(name) < 0) -+ data->ret += data->error_func(data->obj, FSCK_ERROR, -+ "disallowed submodule name: %s", -+ name); -+ free(name); -+ -+ return 0; -+} -+ -+static int fsck_blob(struct blob *blob, const char *buf, -+ unsigned long size, fsck_error error_func) -+{ -+ struct fsck_gitmodules_data data; -+ -+ if (!oidhash_contains(&gitmodules_found, blob->object.sha1)) -+ return 0; -+ oidhash_insert(&gitmodules_done, blob->object.sha1); -+ -+ if (!buf) { -+ /* -+ * A missing buffer here is a sign that the caller found the -+ * blob too gigantic to load into memory. Let's just consider -+ * that an error. -+ */ -+ return error_func(&blob->object, FSCK_ERROR, -+ ".gitmodules too large to parse"); -+ } -+ -+ data.obj = &blob->object; -+ data.error_func = error_func; -+ data.ret = 0; -+ if (git_config_from_buf(fsck_gitmodules_fn, ".gitmodules", -+ buf, size, &data)) -+ data.ret += error_func(&blob->object, FSCK_ERROR, -+ "could not parse gitmodules blob"); -+ -+ return data.ret; -+} -+ -+int fsck_object(struct object *obj, void *data, unsigned long size, -+ int strict, fsck_error error_func) - { - if (!obj) - return error_func(obj, FSCK_ERROR, "no valid object to fsck"); - - if (obj->type == OBJ_BLOB) -- return 0; -+ return fsck_blob((struct blob *)obj, (const char *) data, -+ size, error_func); - if (obj->type == OBJ_TREE) - return fsck_tree((struct tree *) obj, strict, error_func); - if (obj->type == OBJ_COMMIT) -- return fsck_commit((struct commit *) obj, error_func); -+ return fsck_commit((struct commit *) obj, data, -+ size, error_func); - if (obj->type == OBJ_TAG) -- return fsck_tag((struct tag *) obj, error_func); -+ return fsck_tag((struct tag *) obj, (const char *) data, -+ size, error_func); - - return error_func(obj, FSCK_ERROR, "unknown type '%d' (internal fsck error)", - obj->type); -@@ -382,3 +513,47 @@ int fsck_error_function(struct object *obj, int type, const char *fmt, ...) - strbuf_release(&sb); - return 1; - } -+ -+int fsck_finish(fsck_error error_func) -+{ -+ int retval = 0; -+ struct hashmap_iter iter; -+ const struct oidhash_entry *e; -+ -+ hashmap_iter_init(&gitmodules_found, &iter); -+ while ((e = hashmap_iter_next(&iter))) { -+ const unsigned char *sha1 = e->sha1; -+ struct blob *blob; -+ enum object_type type; -+ unsigned long size; -+ char *buf; -+ -+ if (oidhash_contains(&gitmodules_done, sha1)) -+ continue; -+ -+ blob = lookup_blob(sha1); -+ if (!blob) { -+ retval += error_func(&blob->object, FSCK_ERROR, -+ "non-blob found at .gitmodules"); -+ continue; -+ } -+ -+ buf = read_sha1_file(sha1, &type, &size); -+ if (!buf) { -+ retval += error_func(&blob->object, FSCK_ERROR, -+ "unable to read .gitmodules blob"); -+ continue; -+ } -+ -+ if (type == OBJ_BLOB) -+ retval += fsck_blob(blob, buf, size, error_func); -+ else -+ retval += error_func(&blob->object, FSCK_ERROR, -+ "non-blob found at .gitmodules"); -+ free(buf); -+ } -+ -+ hashmap_free(&gitmodules_found, 1); -+ hashmap_free(&gitmodules_done, 1); -+ return retval; -+} -diff --git a/fsck.h b/fsck.h -index 1e4f527..06a0977 100644 ---- a/fsck.h -+++ b/fsck.h -@@ -28,6 +28,15 @@ int fsck_error_function(struct object *obj, int type, const char *fmt, ...); - * 0 everything OK - */ - int fsck_walk(struct object *obj, fsck_walk_func walk, void *data); --int fsck_object(struct object *obj, int strict, fsck_error error_func); -+/* If NULL is passed for data, we assume the object is local and read it. */ -+int fsck_object(struct object *obj, void *data, unsigned long size, -+ int strict, fsck_error error_func); -+ -+/* -+ * Some fsck checks are context-dependent, and may end up queued; run this -+ * after completing all fsck_object() calls in order to resolve any remaining -+ * checks. -+ */ -+int fsck_finish(fsck_error error_func); - - #endif -diff --git a/git-compat-util.h b/git-compat-util.h -index 89abf8f..bd62ab0 100644 ---- a/git-compat-util.h -+++ b/git-compat-util.h -@@ -637,6 +637,23 @@ static inline int sane_iscase(int x, int is_lower) - return (x & 0x20) == 0; - } - -+/* -+ * Like skip_prefix, but compare case-insensitively. Note that the comparison -+ * is done via tolower(), so it is strictly ASCII (no multi-byte characters or -+ * locale-specific conversions). -+ */ -+static inline int skip_iprefix(const char *str, const char *prefix, -+ const char **out) -+{ -+ do { -+ if (!*prefix) { -+ *out = str; -+ return 1; -+ } -+ } while (tolower(*str++) == tolower(*prefix++)); -+ return 0; -+} -+ - static inline int strtoul_ui(char const *s, int base, unsigned int *result) - { - unsigned long ul; -diff --git a/git-submodule.sh b/git-submodule.sh -index bec3362..ca16579 100755 ---- a/git-submodule.sh -+++ b/git-submodule.sh -@@ -188,6 +188,19 @@ get_submodule_config () { - printf '%s' "${value:-$default}" - } - -+# -+# Check whether a submodule name is acceptable, dying if not. -+# -+# $1 = submodule name -+# -+check_module_name() -+{ -+ sm_name=$1 -+ if ! git submodule--helper check-name "$sm_name" -+ then -+ die "$(eval_gettext "'$sm_name' is not a valid submodule name")" -+ fi -+} - - # - # Map submodule path to submodule name -@@ -203,6 +216,7 @@ module_name() - sed -n -e 's|^submodule\.\(.*\)\.path '"$re"'$|\1|p' ) - test -z "$name" && - die "$(eval_gettext "No submodule mapping found in .gitmodules for path '\$sm_path'")" -+ check_module_name "$name" - echo "$name" - } - -@@ -389,6 +403,11 @@ Use -f if you really want to add it." >&2 - sm_name="$sm_path" - fi - -+ if ! git submodule--helper check-name "$sm_name" -+ then -+ die "$(eval_gettext "'$sm_name' is not a valid submodule name")" -+ fi -+ - # perhaps the path exists and is already a git repo, else clone it - if test -e "$sm_path" - then -@@ -481,7 +500,7 @@ cmd_foreach() - if test -e "$sm_path"/.git - then - say "$(eval_gettext "Entering '\$prefix\$sm_path'")" -- name=$(module_name "$sm_path") -+ name=$(module_name "$sm_path") || exit - ( - prefix="$prefix$sm_path/" - clear_local_git_env -diff --git a/git.c b/git.c -index 1ada169..db12099 100644 ---- a/git.c -+++ b/git.c -@@ -404,6 +404,7 @@ static void handle_internal_command(int argc, const char **argv) - { "stage", cmd_add, RUN_SETUP | NEED_WORK_TREE }, - { "status", cmd_status, RUN_SETUP | NEED_WORK_TREE }, - { "stripspace", cmd_stripspace }, -+ { "submodule--helper", cmd_submodule__helper }, - { "symbolic-ref", cmd_symbolic_ref, RUN_SETUP }, - { "tag", cmd_tag, RUN_SETUP }, - { "tar-tree", cmd_tar_tree }, -diff --git a/hashmap.c b/hashmap.c -new file mode 100644 -index 0000000..d1b8056 ---- /dev/null -+++ b/hashmap.c -@@ -0,0 +1,228 @@ -+/* -+ * Generic implementation of hash-based key value mappings. -+ */ -+#include "cache.h" -+#include "hashmap.h" -+ -+#define FNV32_BASE ((unsigned int) 0x811c9dc5) -+#define FNV32_PRIME ((unsigned int) 0x01000193) -+ -+unsigned int strhash(const char *str) -+{ -+ unsigned int c, hash = FNV32_BASE; -+ while ((c = (unsigned char) *str++)) -+ hash = (hash * FNV32_PRIME) ^ c; -+ return hash; -+} -+ -+unsigned int strihash(const char *str) -+{ -+ unsigned int c, hash = FNV32_BASE; -+ while ((c = (unsigned char) *str++)) { -+ if (c >= 'a' && c <= 'z') -+ c -= 'a' - 'A'; -+ hash = (hash * FNV32_PRIME) ^ c; -+ } -+ return hash; -+} -+ -+unsigned int memhash(const void *buf, size_t len) -+{ -+ unsigned int hash = FNV32_BASE; -+ unsigned char *ucbuf = (unsigned char *) buf; -+ while (len--) { -+ unsigned int c = *ucbuf++; -+ hash = (hash * FNV32_PRIME) ^ c; -+ } -+ return hash; -+} -+ -+unsigned int memihash(const void *buf, size_t len) -+{ -+ unsigned int hash = FNV32_BASE; -+ unsigned char *ucbuf = (unsigned char *) buf; -+ while (len--) { -+ unsigned int c = *ucbuf++; -+ if (c >= 'a' && c <= 'z') -+ c -= 'a' - 'A'; -+ hash = (hash * FNV32_PRIME) ^ c; -+ } -+ return hash; -+} -+ -+#define HASHMAP_INITIAL_SIZE 64 -+/* grow / shrink by 2^2 */ -+#define HASHMAP_RESIZE_BITS 2 -+/* load factor in percent */ -+#define HASHMAP_LOAD_FACTOR 80 -+ -+static void alloc_table(struct hashmap *map, unsigned int size) -+{ -+ map->tablesize = size; -+ map->table = xcalloc(size, sizeof(struct hashmap_entry *)); -+ -+ /* calculate resize thresholds for new size */ -+ map->grow_at = (unsigned int) ((uint64_t) size * HASHMAP_LOAD_FACTOR / 100); -+ if (size <= HASHMAP_INITIAL_SIZE) -+ map->shrink_at = 0; -+ else -+ /* -+ * The shrink-threshold must be slightly smaller than -+ * (grow-threshold / resize-factor) to prevent erratic resizing, -+ * thus we divide by (resize-factor + 1). -+ */ -+ map->shrink_at = map->grow_at / ((1 << HASHMAP_RESIZE_BITS) + 1); -+} -+ -+static inline int entry_equals(const struct hashmap *map, -+ const struct hashmap_entry *e1, const struct hashmap_entry *e2, -+ const void *keydata) -+{ -+ return (e1 == e2) || (e1->hash == e2->hash && !map->cmpfn(e1, e2, keydata)); -+} -+ -+static inline unsigned int bucket(const struct hashmap *map, -+ const struct hashmap_entry *key) -+{ -+ return key->hash & (map->tablesize - 1); -+} -+ -+static void rehash(struct hashmap *map, unsigned int newsize) -+{ -+ unsigned int i, oldsize = map->tablesize; -+ struct hashmap_entry **oldtable = map->table; -+ -+ alloc_table(map, newsize); -+ for (i = 0; i < oldsize; i++) { -+ struct hashmap_entry *e = oldtable[i]; -+ while (e) { -+ struct hashmap_entry *next = e->next; -+ unsigned int b = bucket(map, e); -+ e->next = map->table[b]; -+ map->table[b] = e; -+ e = next; -+ } -+ } -+ free(oldtable); -+} -+ -+static inline struct hashmap_entry **find_entry_ptr(const struct hashmap *map, -+ const struct hashmap_entry *key, const void *keydata) -+{ -+ struct hashmap_entry **e = &map->table[bucket(map, key)]; -+ while (*e && !entry_equals(map, *e, key, keydata)) -+ e = &(*e)->next; -+ return e; -+} -+ -+static int always_equal(const void *unused1, const void *unused2, const void *unused3) -+{ -+ return 0; -+} -+ -+void hashmap_init(struct hashmap *map, hashmap_cmp_fn equals_function, -+ size_t initial_size) -+{ -+ unsigned int size = HASHMAP_INITIAL_SIZE; -+ map->size = 0; -+ map->cmpfn = equals_function ? equals_function : always_equal; -+ -+ /* calculate initial table size and allocate the table */ -+ initial_size = (unsigned int) ((uint64_t) initial_size * 100 -+ / HASHMAP_LOAD_FACTOR); -+ while (initial_size > size) -+ size <<= HASHMAP_RESIZE_BITS; -+ alloc_table(map, size); -+} -+ -+void hashmap_free(struct hashmap *map, int free_entries) -+{ -+ if (!map || !map->table) -+ return; -+ if (free_entries) { -+ struct hashmap_iter iter; -+ struct hashmap_entry *e; -+ hashmap_iter_init(map, &iter); -+ while ((e = hashmap_iter_next(&iter))) -+ free(e); -+ } -+ free(map->table); -+ memset(map, 0, sizeof(*map)); -+} -+ -+void *hashmap_get(const struct hashmap *map, const void *key, const void *keydata) -+{ -+ return *find_entry_ptr(map, key, keydata); -+} -+ -+void *hashmap_get_next(const struct hashmap *map, const void *entry) -+{ -+ struct hashmap_entry *e = ((struct hashmap_entry *) entry)->next; -+ for (; e; e = e->next) -+ if (entry_equals(map, entry, e, NULL)) -+ return e; -+ return NULL; -+} -+ -+void hashmap_add(struct hashmap *map, void *entry) -+{ -+ unsigned int b = bucket(map, entry); -+ -+ /* add entry */ -+ ((struct hashmap_entry *) entry)->next = map->table[b]; -+ map->table[b] = entry; -+ -+ /* fix size and rehash if appropriate */ -+ map->size++; -+ if (map->size > map->grow_at) -+ rehash(map, map->tablesize << HASHMAP_RESIZE_BITS); -+} -+ -+void *hashmap_remove(struct hashmap *map, const void *key, const void *keydata) -+{ -+ struct hashmap_entry *old; -+ struct hashmap_entry **e = find_entry_ptr(map, key, keydata); -+ if (!*e) -+ return NULL; -+ -+ /* remove existing entry */ -+ old = *e; -+ *e = old->next; -+ old->next = NULL; -+ -+ /* fix size and rehash if appropriate */ -+ map->size--; -+ if (map->size < map->shrink_at) -+ rehash(map, map->tablesize >> HASHMAP_RESIZE_BITS); -+ return old; -+} -+ -+void *hashmap_put(struct hashmap *map, void *entry) -+{ -+ struct hashmap_entry *old = hashmap_remove(map, entry, NULL); -+ hashmap_add(map, entry); -+ return old; -+} -+ -+void hashmap_iter_init(struct hashmap *map, struct hashmap_iter *iter) -+{ -+ iter->map = map; -+ iter->tablepos = 0; -+ iter->next = NULL; -+} -+ -+void *hashmap_iter_next(struct hashmap_iter *iter) -+{ -+ struct hashmap_entry *current = iter->next; -+ for (;;) { -+ if (current) { -+ iter->next = current->next; -+ return current; -+ } -+ -+ if (iter->tablepos >= iter->map->tablesize) -+ return NULL; -+ -+ current = iter->map->table[iter->tablepos++]; -+ } -+} -diff --git a/hashmap.h b/hashmap.h -new file mode 100644 -index 0000000..a8b9e3d ---- /dev/null -+++ b/hashmap.h -@@ -0,0 +1,90 @@ -+#ifndef HASHMAP_H -+#define HASHMAP_H -+ -+/* -+ * Generic implementation of hash-based key-value mappings. -+ * See Documentation/technical/api-hashmap.txt. -+ */ -+ -+/* FNV-1 functions */ -+ -+extern unsigned int strhash(const char *buf); -+extern unsigned int strihash(const char *buf); -+extern unsigned int memhash(const void *buf, size_t len); -+extern unsigned int memihash(const void *buf, size_t len); -+ -+static inline unsigned int sha1hash(const unsigned char *sha1) -+{ -+ /* -+ * Equivalent to 'return *(unsigned int *)sha1;', but safe on -+ * platforms that don't support unaligned reads. -+ */ -+ unsigned int hash; -+ memcpy(&hash, sha1, sizeof(hash)); -+ return hash; -+} -+ -+/* data structures */ -+ -+struct hashmap_entry { -+ struct hashmap_entry *next; -+ unsigned int hash; -+}; -+ -+typedef int (*hashmap_cmp_fn)(const void *entry, const void *entry_or_key, -+ const void *keydata); -+ -+struct hashmap { -+ struct hashmap_entry **table; -+ hashmap_cmp_fn cmpfn; -+ unsigned int size, tablesize, grow_at, shrink_at; -+}; -+ -+struct hashmap_iter { -+ struct hashmap *map; -+ struct hashmap_entry *next; -+ unsigned int tablepos; -+}; -+ -+/* hashmap functions */ -+ -+extern void hashmap_init(struct hashmap *map, hashmap_cmp_fn equals_function, -+ size_t initial_size); -+extern void hashmap_free(struct hashmap *map, int free_entries); -+ -+/* hashmap_entry functions */ -+ -+static inline void hashmap_entry_init(void *entry, unsigned int hash) -+{ -+ struct hashmap_entry *e = entry; -+ e->hash = hash; -+ e->next = NULL; -+} -+extern void *hashmap_get(const struct hashmap *map, const void *key, -+ const void *keydata); -+extern void *hashmap_get_next(const struct hashmap *map, const void *entry); -+extern void hashmap_add(struct hashmap *map, void *entry); -+extern void *hashmap_put(struct hashmap *map, void *entry); -+extern void *hashmap_remove(struct hashmap *map, const void *key, -+ const void *keydata); -+ -+static inline void *hashmap_get_from_hash(const struct hashmap *map, -+ unsigned int hash, const void *keydata) -+{ -+ struct hashmap_entry key; -+ hashmap_entry_init(&key, hash); -+ return hashmap_get(map, &key, keydata); -+} -+ -+/* hashmap_iter functions */ -+ -+extern void hashmap_iter_init(struct hashmap *map, struct hashmap_iter *iter); -+extern void *hashmap_iter_next(struct hashmap_iter *iter); -+static inline void *hashmap_iter_first(struct hashmap *map, -+ struct hashmap_iter *iter) -+{ -+ hashmap_iter_init(map, iter); -+ return hashmap_iter_next(iter); -+} -+ -+#endif -diff --git a/read-cache.c b/read-cache.c -index 04ed561..a800c11 100644 ---- a/read-cache.c -+++ b/read-cache.c -@@ -684,7 +684,7 @@ struct cache_entry *make_cache_entry(unsigned int mode, - int size, len; - struct cache_entry *ce; - -- if (!verify_path(path)) { -+ if (!verify_path(path, mode)) { - error("Invalid path '%s'", path); - return NULL; - } -@@ -724,7 +724,7 @@ int ce_path_match(const struct cache_entry *ce, const struct pathspec *pathspec) - * Also, we don't want double slashes or slashes at the - * end that can make pathnames ambiguous. - */ --static int verify_dotfile(const char *rest) -+static int verify_dotfile(const char *rest, unsigned mode) - { - /* - * The first character was '.', but that -@@ -738,16 +738,28 @@ static int verify_dotfile(const char *rest) - - switch (*rest) { - /* -- * ".git" followed by NUL or slash is bad. This -- * shares the path end test with the ".." case. -+ * ".git" followed by NUL or slash is bad. Note that we match -+ * case-insensitively here, even if ignore_case is not set. -+ * This outlaws ".GIT" everywhere out of an abundance of caution, -+ * since there's really no good reason to allow it. -+ * -+ * Once we've seen ".git", we can also find ".gitmodules", etc (also -+ * case-insensitively). - */ - case 'g': - if (rest[1] != 'i') - break; - if (rest[2] != 't') - break; -- rest += 2; -- /* fallthrough */ -+ if (rest[3] == '\0' || is_dir_sep(rest[3])) -+ return 0; -+ if (S_ISLNK(mode)) { -+ rest += 3; -+ if (skip_iprefix(rest, "modules", &rest) && -+ (*rest == '\0' || is_dir_sep(*rest))) -+ return 0; -+ } -+ break; - case '.': - if (rest[1] == '\0' || is_dir_sep(rest[1])) - return 0; -@@ -755,7 +767,7 @@ static int verify_dotfile(const char *rest) - return 1; - } - --int verify_path(const char *path) -+int verify_path(const char *path, unsigned mode) - { - char c; - -@@ -769,7 +781,7 @@ int verify_path(const char *path) - if (is_dir_sep(c)) { - inside: - c = *path++; -- if ((c == '.' && !verify_dotfile(path)) || -+ if ((c == '.' && !verify_dotfile(path, mode)) || - is_dir_sep(c) || c == '\0') - return 0; - } -@@ -947,7 +959,7 @@ static int add_index_entry_with_check(struct index_state *istate, struct cache_e - - if (!ok_to_add) - return -1; -- if (!verify_path(ce->name)) -+ if (!verify_path(ce->name, ce->ce_mode)) - return error("Invalid path '%s'", ce->name); - - if (!skip_df_check && -diff --git a/sha1_file.c b/sha1_file.c -index b114cc9..c92efa6 100644 ---- a/sha1_file.c -+++ b/sha1_file.c -@@ -1325,12 +1325,21 @@ static int open_sha1_file(const unsigned char *sha1) - return -1; - } - --void *map_sha1_file(const unsigned char *sha1, unsigned long *size) -+/* -+ * Map the loose object at "path" if it is not NULL, or the path found by -+ * searching for a loose object named "sha1". -+ */ -+static void *map_sha1_file_1(const char *path, -+ const unsigned char *sha1, -+ unsigned long *size) - { - void *map; - int fd; - -- fd = open_sha1_file(sha1); -+ if (path) -+ fd = git_open_noatime(path); -+ else -+ fd = open_sha1_file(sha1); - map = NULL; - if (fd >= 0) { - struct stat st; -@@ -1394,6 +1403,11 @@ static int experimental_loose_object(unsigned char *map) - return 1; - } - -+void *map_sha1_file(const unsigned char *sha1, unsigned long *size) -+{ -+ return map_sha1_file_1(NULL, sha1, size); -+} -+ - unsigned long unpack_object_header_buffer(const unsigned char *buf, - unsigned long len, enum object_type *type, unsigned long *sizep) - { -@@ -3043,3 +3057,117 @@ void assert_sha1_type(const unsigned char *sha1, enum object_type expect) - die("%s is not a valid '%s' object", sha1_to_hex(sha1), - typename(expect)); - } -+ -+static int check_stream_sha1(git_zstream *stream, -+ const char *hdr, -+ unsigned long size, -+ const char *path, -+ const unsigned char *expected_sha1) -+{ -+ git_SHA_CTX c; -+ unsigned char real_sha1[20]; -+ unsigned char buf[4096]; -+ unsigned long total_read; -+ int status = Z_OK; -+ -+ git_SHA1_Init(&c); -+ git_SHA1_Update(&c, hdr, stream->total_out); -+ -+ /* -+ * We already read some bytes into hdr, but the ones up to the NUL -+ * do not count against the object's content size. -+ */ -+ total_read = stream->total_out - strlen(hdr) - 1; -+ -+ /* -+ * This size comparison must be "<=" to read the final zlib packets; -+ * see the comment in unpack_sha1_rest for details. -+ */ -+ while (total_read <= size && -+ (status == Z_OK || status == Z_BUF_ERROR)) { -+ stream->next_out = buf; -+ stream->avail_out = sizeof(buf); -+ if (size - total_read < stream->avail_out) -+ stream->avail_out = size - total_read; -+ status = git_inflate(stream, Z_FINISH); -+ git_SHA1_Update(&c, buf, stream->next_out - buf); -+ total_read += stream->next_out - buf; -+ } -+ git_inflate_end(stream); -+ -+ if (status != Z_STREAM_END) { -+ error("corrupt loose object '%s'", sha1_to_hex(expected_sha1)); -+ return -1; -+ } -+ -+ git_SHA1_Final(real_sha1, &c); -+ if (hashcmp(expected_sha1, real_sha1)) { -+ error("sha1 mismatch for %s (expected %s)", path, -+ sha1_to_hex(expected_sha1)); -+ return -1; -+ } -+ -+ return 0; -+} -+ -+int read_loose_object(const char *path, -+ const unsigned char *expected_sha1, -+ enum object_type *type, -+ unsigned long *size, -+ void **contents) -+{ -+ int ret = -1; -+ int fd = -1; -+ void *map = NULL; -+ unsigned long mapsize; -+ git_zstream stream; -+ char hdr[32]; -+ -+ *contents = NULL; -+ -+ map = map_sha1_file_1(path, NULL, &mapsize); -+ if (!map) { -+ error("unable to mmap %s: %s", path, strerror(errno)); -+ goto out; -+ } -+ -+ if (unpack_sha1_header(&stream, map, mapsize, hdr, sizeof(hdr)) < 0) { -+ error("unable to unpack header of %s", path); -+ goto out; -+ } -+ -+ *type = parse_sha1_header(hdr, size); -+ if (*type < 0) { -+ error("unable to parse header of %s", path); -+ git_inflate_end(&stream); -+ goto out; -+ } -+ -+ if (*type == OBJ_BLOB && *size > big_file_threshold) { -+ if (check_stream_sha1(&stream, hdr, *size, path, expected_sha1) < 0) -+ goto out; -+ } else { -+ *contents = unpack_sha1_rest(&stream, hdr, *size, expected_sha1); -+ if (!*contents) { -+ error("unable to unpack contents of %s", path); -+ git_inflate_end(&stream); -+ goto out; -+ } -+ if (check_sha1_signature(expected_sha1, *contents, -+ *size, typename(*type))) { -+ error("sha1 mismatch for %s (expected %s)", path, -+ sha1_to_hex(expected_sha1)); -+ free(*contents); -+ goto out; -+ } -+ } -+ -+ ret = 0; /* everything checks out */ -+ -+out: -+ if (map) -+ munmap(map, mapsize); -+ if (fd >= 0) -+ close(fd); -+ return ret; -+} -diff --git a/submodule.c b/submodule.c -index 1821a5b..6337cab 100644 ---- a/submodule.c -+++ b/submodule.c -@@ -124,6 +124,31 @@ void gitmodules_config(void) - } - } - -+int check_submodule_name(const char *name) -+{ -+ /* Disallow empty names */ -+ if (!*name) -+ return -1; -+ -+ /* -+ * Look for '..' as a path component. Check both '/' and '\\' as -+ * separators rather than is_dir_sep(), because we want the name rules -+ * to be consistent across platforms. -+ */ -+ goto in_component; /* always start inside component */ -+ while (*name) { -+ char c = *name++; -+ if (c == '/' || c == '\\') { -+in_component: -+ if (name[0] == '.' && name[1] == '.' && -+ (!name[2] || name[2] == '/' || name[2] == '\\')) -+ return -1; -+ } -+ } -+ -+ return 0; -+} -+ - int parse_submodule_config_option(const char *var, const char *value) - { - struct string_list_item *config; -@@ -132,6 +157,10 @@ int parse_submodule_config_option(const char *var, const char *value) - - if (parse_config_key(var, "submodule", &name, &namelen, &key) < 0 || !name) - return 0; -+ if (check_submodule_name(name) < 0) { -+ warning(_("ignoring suspicious submodule name: %s"), name); -+ return 0; -+ } - - if (!strcmp(key, "path")) { - config = unsorted_string_list_lookup(&config_name_for_path, value); -diff --git a/submodule.h b/submodule.h -index c7ffc7c..59dbdfb 100644 ---- a/submodule.h -+++ b/submodule.h -@@ -37,4 +37,11 @@ int find_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_nam - struct string_list *needs_pushing); - int push_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_name); - -+/* -+ * Returns 0 if the name is syntactically acceptable as a submodule "name" -+ * (e.g., that may be found in the subsection of a .gitmodules file) and -1 -+ * otherwise. -+ */ -+int check_submodule_name(const char *name); -+ - #endif -diff --git a/t/lib-pack.sh b/t/lib-pack.sh -new file mode 100644 -index 0000000..4674899 ---- /dev/null -+++ b/t/lib-pack.sh -@@ -0,0 +1,110 @@ -+# Support routines for hand-crafting weird or malicious packs. -+# -+# You can make a complete pack like: -+# -+# pack_header 2 >foo.pack && -+# pack_obj e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 >>foo.pack && -+# pack_obj e68fe8129b546b101aee9510c5328e7f21ca1d18 >>foo.pack && -+# pack_trailer foo.pack -+ -+# Print the big-endian 4-byte octal representation of $1 -+uint32_octal () { -+ n=$1 -+ printf '\\%o' $(($n / 16777216)); n=$((n % 16777216)) -+ printf '\\%o' $(($n / 65536)); n=$((n % 65536)) -+ printf '\\%o' $(($n / 256)); n=$((n % 256)) -+ printf '\\%o' $(($n )); -+} -+ -+# Print the big-endian 4-byte binary representation of $1 -+uint32_binary () { -+ printf "$(uint32_octal "$1")" -+} -+ -+# Print a pack header, version 2, for a pack with $1 objects -+pack_header () { -+ printf 'PACK' && -+ printf '\0\0\0\2' && -+ uint32_binary "$1" -+} -+ -+# Print the pack data for object $1, as a delta against object $2 (or as a full -+# object if $2 is missing or empty). The output is suitable for including -+# directly in the packfile, and represents the entirety of the object entry. -+# Doing this on the fly (especially picking your deltas) is quite tricky, so we -+# have hardcoded some well-known objects. See the case statements below for the -+# complete list. -+pack_obj () { -+ case "$1" in -+ # empty blob -+ e69de29bb2d1d6434b8b29ae775ad8c2e48c5391) -+ case "$2" in -+ '') -+ printf '\060\170\234\003\0\0\0\0\1' -+ return -+ ;; -+ esac -+ ;; -+ -+ # blob containing "\7\76" -+ e68fe8129b546b101aee9510c5328e7f21ca1d18) -+ case "$2" in -+ '') -+ printf '\062\170\234\143\267\3\0\0\116\0\106' -+ return -+ ;; -+ 01d7713666f4de822776c7622c10f1b07de280dc) -+ printf '\165\1\327\161\66\146\364\336\202\47\166' && -+ printf '\307\142\54\20\361\260\175\342\200\334\170' && -+ printf '\234\143\142\142\142\267\003\0\0\151\0\114' -+ return -+ ;; -+ esac -+ ;; -+ -+ # blob containing "\7\0" -+ 01d7713666f4de822776c7622c10f1b07de280dc) -+ case "$2" in -+ '') -+ printf '\062\170\234\143\147\0\0\0\20\0\10' -+ return -+ ;; -+ e68fe8129b546b101aee9510c5328e7f21ca1d18) -+ printf '\165\346\217\350\22\233\124\153\20\32\356' && -+ printf '\225\20\305\62\216\177\41\312\35\30\170\234' && -+ printf '\143\142\142\142\147\0\0\0\53\0\16' -+ return -+ ;; -+ esac -+ ;; -+ esac -+ -+ # If it's not a delta, we can convince pack-objects to generate a pack -+ # with just our entry, and then strip off the header (12 bytes) and -+ # trailer (20 bytes). -+ if test -z "$2" -+ then -+ echo "$1" | git pack-objects --stdout >pack_obj.tmp && -+ size=$(wc -c &2 "BUG: don't know how to print $1${2:+ (from $2)}" -+ return 1 -+} -+ -+# Compute and append pack trailer to "$1" -+pack_trailer () { -+ test-sha1 -b <"$1" >trailer.tmp && -+ cat trailer.tmp >>"$1" && -+ rm -f trailer.tmp -+} -+ -+# Remove any existing packs to make sure that -+# whatever we index next will be the pack that we -+# actually use. -+clear_packs () { -+ rm -f .git/objects/pack/* -+} -diff --git a/t/t0011-hashmap.sh b/t/t0011-hashmap.sh -new file mode 100755 -index 0000000..391e2b6 ---- /dev/null -+++ b/t/t0011-hashmap.sh -@@ -0,0 +1,240 @@ -+#!/bin/sh -+ -+test_description='test hashmap and string hash functions' -+. ./test-lib.sh -+ -+test_hashmap() { -+ echo "$1" | test-hashmap $3 > actual && -+ echo "$2" > expect && -+ test_cmp expect actual -+} -+ -+test_expect_success 'hash functions' ' -+ -+test_hashmap "hash key1" "2215982743 2215982743 116372151 116372151" && -+test_hashmap "hash key2" "2215982740 2215982740 116372148 116372148" && -+test_hashmap "hash fooBarFrotz" "1383912807 1383912807 3189766727 3189766727" && -+test_hashmap "hash foobarfrotz" "2862305959 2862305959 3189766727 3189766727" -+ -+' -+ -+test_expect_success 'put' ' -+ -+test_hashmap "put key1 value1 -+put key2 value2 -+put fooBarFrotz value3 -+put foobarfrotz value4 -+size" "NULL -+NULL -+NULL -+NULL -+64 4" -+ -+' -+ -+test_expect_success 'put (case insensitive)' ' -+ -+test_hashmap "put key1 value1 -+put key2 value2 -+put fooBarFrotz value3 -+size" "NULL -+NULL -+NULL -+64 3" ignorecase -+ -+' -+ -+test_expect_success 'replace' ' -+ -+test_hashmap "put key1 value1 -+put key1 value2 -+put fooBarFrotz value3 -+put fooBarFrotz value4 -+size" "NULL -+value1 -+NULL -+value3 -+64 2" -+ -+' -+ -+test_expect_success 'replace (case insensitive)' ' -+ -+test_hashmap "put key1 value1 -+put Key1 value2 -+put fooBarFrotz value3 -+put foobarfrotz value4 -+size" "NULL -+value1 -+NULL -+value3 -+64 2" ignorecase -+ -+' -+ -+test_expect_success 'get' ' -+ -+test_hashmap "put key1 value1 -+put key2 value2 -+put fooBarFrotz value3 -+put foobarfrotz value4 -+get key1 -+get key2 -+get fooBarFrotz -+get notInMap" "NULL -+NULL -+NULL -+NULL -+value1 -+value2 -+value3 -+NULL" -+ -+' -+ -+test_expect_success 'get (case insensitive)' ' -+ -+test_hashmap "put key1 value1 -+put key2 value2 -+put fooBarFrotz value3 -+get Key1 -+get keY2 -+get foobarfrotz -+get notInMap" "NULL -+NULL -+NULL -+value1 -+value2 -+value3 -+NULL" ignorecase -+ -+' -+ -+test_expect_success 'add' ' -+ -+test_hashmap "add key1 value1 -+add key1 value2 -+add fooBarFrotz value3 -+add fooBarFrotz value4 -+get key1 -+get fooBarFrotz -+get notInMap" "value2 -+value1 -+value4 -+value3 -+NULL" -+ -+' -+ -+test_expect_success 'add (case insensitive)' ' -+ -+test_hashmap "add key1 value1 -+add Key1 value2 -+add fooBarFrotz value3 -+add foobarfrotz value4 -+get key1 -+get Foobarfrotz -+get notInMap" "value2 -+value1 -+value4 -+value3 -+NULL" ignorecase -+ -+' -+ -+test_expect_success 'remove' ' -+ -+test_hashmap "put key1 value1 -+put key2 value2 -+put fooBarFrotz value3 -+remove key1 -+remove key2 -+remove notInMap -+size" "NULL -+NULL -+NULL -+value1 -+value2 -+NULL -+64 1" -+ -+' -+ -+test_expect_success 'remove (case insensitive)' ' -+ -+test_hashmap "put key1 value1 -+put key2 value2 -+put fooBarFrotz value3 -+remove Key1 -+remove keY2 -+remove notInMap -+size" "NULL -+NULL -+NULL -+value1 -+value2 -+NULL -+64 1" ignorecase -+ -+' -+ -+test_expect_success 'iterate' ' -+ -+test_hashmap "put key1 value1 -+put key2 value2 -+put fooBarFrotz value3 -+iterate" "NULL -+NULL -+NULL -+key2 value2 -+key1 value1 -+fooBarFrotz value3" -+ -+' -+ -+test_expect_success 'iterate (case insensitive)' ' -+ -+test_hashmap "put key1 value1 -+put key2 value2 -+put fooBarFrotz value3 -+iterate" "NULL -+NULL -+NULL -+fooBarFrotz value3 -+key2 value2 -+key1 value1" ignorecase -+ -+' -+ -+test_expect_success 'grow / shrink' ' -+ -+ rm -f in && -+ rm -f expect && -+ for n in $(test_seq 51) -+ do -+ echo put key$n value$n >> in && -+ echo NULL >> expect -+ done && -+ echo size >> in && -+ echo 64 51 >> expect && -+ echo put key52 value52 >> in && -+ echo NULL >> expect -+ echo size >> in && -+ echo 256 52 >> expect && -+ for n in $(test_seq 12) -+ do -+ echo remove key$n >> in && -+ echo value$n >> expect -+ done && -+ echo size >> in && -+ echo 256 40 >> expect && -+ echo remove key40 >> in && -+ echo value40 >> expect && -+ echo size >> in && -+ echo 64 39 >> expect && -+ cat in | test-hashmap > out && -+ test_cmp expect out -+ -+' -+ -+test_done -diff --git a/t/t1307-config-blob.sh b/t/t1307-config-blob.sh -new file mode 100755 -index 0000000..fdc257e ---- /dev/null -+++ b/t/t1307-config-blob.sh -@@ -0,0 +1,70 @@ -+#!/bin/sh -+ -+test_description='support for reading config from a blob' -+. ./test-lib.sh -+ -+test_expect_success 'create config blob' ' -+ cat >config <<-\EOF && -+ [some] -+ value = 1 -+ EOF -+ git add config && -+ git commit -m foo -+' -+ -+test_expect_success 'list config blob contents' ' -+ echo some.value=1 >expect && -+ git config --blob=HEAD:config --list >actual && -+ test_cmp expect actual -+' -+ -+test_expect_success 'fetch value from blob' ' -+ echo true >expect && -+ git config --blob=HEAD:config --bool some.value >actual && -+ test_cmp expect actual -+' -+ -+test_expect_success 'reading non-existing value from blob is an error' ' -+ test_must_fail git config --blob=HEAD:config non.existing -+' -+ -+test_expect_success 'reading from blob and file is an error' ' -+ test_must_fail git config --blob=HEAD:config --system --list -+' -+ -+test_expect_success 'reading from missing ref is an error' ' -+ test_must_fail git config --blob=HEAD:doesnotexist --list -+' -+ -+test_expect_success 'reading from non-blob is an error' ' -+ test_must_fail git config --blob=HEAD --list -+' -+ -+test_expect_success 'setting a value in a blob is an error' ' -+ test_must_fail git config --blob=HEAD:config some.value foo -+' -+ -+test_expect_success 'deleting a value in a blob is an error' ' -+ test_must_fail git config --blob=HEAD:config --unset some.value -+' -+ -+test_expect_success 'editing a blob is an error' ' -+ test_must_fail git config --blob=HEAD:config --edit -+' -+ -+test_expect_success 'parse errors in blobs are properly attributed' ' -+ cat >config <<-\EOF && -+ [some] -+ value = " -+ EOF -+ git add config && -+ git commit -m broken && -+ -+ test_must_fail git config --blob=HEAD:config some.value 2>err && -+ -+ # just grep for our token as the exact error message is likely to -+ # change or be internationalized -+ grep "HEAD:config" err -+' -+ -+test_done -diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh -index d730734..9dfa4b0 100755 ---- a/t/t1450-fsck.sh -+++ b/t/t1450-fsck.sh -@@ -69,7 +69,7 @@ test_expect_success 'object with bad sha1' ' - git update-ref refs/heads/bogus $cmt && - test_when_finished "git update-ref -d refs/heads/bogus" && - -- test_might_fail git fsck 2>out && -+ test_must_fail git fsck 2>out && - cat out && - grep "$sha.*corrupt" out - ' -@@ -101,7 +101,7 @@ test_expect_success 'email with embedded > is not okay' ' - test_when_finished "remove_object $new" && - git update-ref refs/heads/bogus "$new" && - test_when_finished "git update-ref -d refs/heads/bogus" && -- git fsck 2>out && -+ test_must_fail git fsck 2>out && - cat out && - grep "error in commit $new" out - ' -@@ -113,7 +113,7 @@ test_expect_success 'missing < email delimiter is reported nicely' ' - test_when_finished "remove_object $new" && - git update-ref refs/heads/bogus "$new" && - test_when_finished "git update-ref -d refs/heads/bogus" && -- git fsck 2>out && -+ test_must_fail git fsck 2>out && - cat out && - grep "error in commit $new.* - bad name" out - ' -@@ -125,7 +125,7 @@ test_expect_success 'missing email is reported nicely' ' - test_when_finished "remove_object $new" && - git update-ref refs/heads/bogus "$new" && - test_when_finished "git update-ref -d refs/heads/bogus" && -- git fsck 2>out && -+ test_must_fail git fsck 2>out && - cat out && - grep "error in commit $new.* - missing email" out - ' -@@ -137,11 +137,33 @@ test_expect_success '> in name is reported' ' - test_when_finished "remove_object $new" && - git update-ref refs/heads/bogus "$new" && - test_when_finished "git update-ref -d refs/heads/bogus" && -- git fsck 2>out && -+ test_must_fail git fsck 2>out && - cat out && - grep "error in commit $new" out - ' - -+test_expect_success 'malformatted tree object' ' -+ test_when_finished "git update-ref -d refs/tags/wrong" && -+ test_when_finished "for i in \$T; do remove_object \$i; done" && -+ T=$( -+ GIT_INDEX_FILE=test-index && -+ export GIT_INDEX_FILE && -+ rm -f test-index && -+ >x && -+ git add x && -+ git rev-parse :x && -+ T=$(git write-tree) && -+ echo $T && -+ ( -+ git cat-file tree $T && -+ git cat-file tree $T -+ ) | -+ git hash-object -w -t tree --stdin -+ ) && -+ test_must_fail git fsck 2>out && -+ grep "error in tree .*contains duplicate file entries" out -+' -+ - test_expect_success 'tag pointing to nonexistent' ' - cat >invalid-tag <<-\EOF && - object ffffffffffffffffffffffffffffffffffffffff -@@ -268,4 +290,20 @@ test_expect_success 'fsck notices ".git" in trees' ' - ) - ' - -+test_expect_success 'fsck finds problems in duplicate loose objects' ' -+ rm -rf broken-duplicate && -+ git init broken-duplicate && -+ ( -+ cd broken-duplicate && -+ test_commit duplicate && -+ # no "-d" here, so we end up with duplicates -+ git repack && -+ # now corrupt the loose copy -+ file=$(sha1_file "$(git rev-parse HEAD)") && -+ rm "$file" && -+ echo broken >"$file" && -+ test_must_fail git fsck -+ ) -+' -+ - test_done -diff --git a/t/t4122-apply-symlink-inside.sh b/t/t4122-apply-symlink-inside.sh -index 3940737..b5832e5 100755 ---- a/t/t4122-apply-symlink-inside.sh -+++ b/t/t4122-apply-symlink-inside.sh -@@ -52,4 +52,110 @@ test_expect_success SYMLINKS 'check result' ' - - ' - -+test_expect_success SYMLINKS 'do not read from beyond symbolic link' ' -+ git reset --hard && -+ mkdir -p arch/x86_64/dir && -+ >arch/x86_64/dir/file && -+ git add arch/x86_64/dir/file && -+ echo line >arch/x86_64/dir/file && -+ git diff >patch && -+ git reset --hard && -+ -+ mkdir arch/i386/dir && -+ >arch/i386/dir/file && -+ ln -s ../i386/dir arch/x86_64/dir && -+ -+ test_must_fail git apply patch && -+ test_must_fail git apply --cached patch && -+ test_must_fail git apply --index patch -+ -+' -+ -+test_expect_success SYMLINKS 'do not follow symbolic link (setup)' ' -+ -+ rm -rf arch/i386/dir arch/x86_64/dir && -+ git reset --hard && -+ ln -s ../i386/dir arch/x86_64/dir && -+ git add arch/x86_64/dir && -+ git diff HEAD >add_symlink.patch && -+ git reset --hard && -+ -+ mkdir arch/x86_64/dir && -+ >arch/x86_64/dir/file && -+ git add arch/x86_64/dir/file && -+ git diff HEAD >add_file.patch && -+ git diff -R HEAD >del_file.patch && -+ git reset --hard && -+ rm -fr arch/x86_64/dir && -+ -+ cat add_symlink.patch add_file.patch >patch && -+ cat add_symlink.patch del_file.patch >tricky_del && -+ -+ mkdir arch/i386/dir -+' -+ -+test_expect_success SYMLINKS 'do not follow symbolic link (same input)' ' -+ -+ # same input creates a confusing symbolic link -+ test_must_fail git apply patch 2>error-wt && -+ test_i18ngrep "beyond a symbolic link" error-wt && -+ test_path_is_missing arch/x86_64/dir && -+ test_path_is_missing arch/i386/dir/file && -+ -+ test_must_fail git apply --index patch 2>error-ix && -+ test_i18ngrep "beyond a symbolic link" error-ix && -+ test_path_is_missing arch/x86_64/dir && -+ test_path_is_missing arch/i386/dir/file && -+ test_must_fail git ls-files --error-unmatch arch/x86_64/dir && -+ test_must_fail git ls-files --error-unmatch arch/i386/dir && -+ -+ test_must_fail git apply --cached patch 2>error-ct && -+ test_i18ngrep "beyond a symbolic link" error-ct && -+ test_must_fail git ls-files --error-unmatch arch/x86_64/dir && -+ test_must_fail git ls-files --error-unmatch arch/i386/dir && -+ -+ >arch/i386/dir/file && -+ git add arch/i386/dir/file && -+ -+ test_must_fail git apply tricky_del && -+ test_path_is_file arch/i386/dir/file && -+ -+ test_must_fail git apply --index tricky_del && -+ test_path_is_file arch/i386/dir/file && -+ test_must_fail git ls-files --error-unmatch arch/x86_64/dir && -+ git ls-files --error-unmatch arch/i386/dir && -+ -+ test_must_fail git apply --cached tricky_del && -+ test_must_fail git ls-files --error-unmatch arch/x86_64/dir && -+ git ls-files --error-unmatch arch/i386/dir -+' -+ -+test_expect_success SYMLINKS 'do not follow symbolic link (existing)' ' -+ -+ # existing symbolic link -+ git reset --hard && -+ ln -s ../i386/dir arch/x86_64/dir && -+ git add arch/x86_64/dir && -+ -+ test_must_fail git apply add_file.patch 2>error-wt-add && -+ test_i18ngrep "beyond a symbolic link" error-wt-add && -+ test_path_is_missing arch/i386/dir/file && -+ -+ mkdir arch/i386/dir && -+ >arch/i386/dir/file && -+ test_must_fail git apply del_file.patch 2>error-wt-del && -+ test_i18ngrep "beyond a symbolic link" error-wt-del && -+ test_path_is_file arch/i386/dir/file && -+ rm arch/i386/dir/file && -+ -+ test_must_fail git apply --index add_file.patch 2>error-ix-add && -+ test_i18ngrep "beyond a symbolic link" error-ix-add && -+ test_path_is_missing arch/i386/dir/file && -+ test_must_fail git ls-files --error-unmatch arch/i386/dir && -+ -+ test_must_fail git apply --cached add_file.patch 2>error-ct-file && -+ test_i18ngrep "beyond a symbolic link" error-ct-file && -+ test_must_fail git ls-files --error-unmatch arch/i386/dir -+' -+ - test_done -diff --git a/t/t4139-apply-escape.sh b/t/t4139-apply-escape.sh -new file mode 100755 -index 0000000..45b5660 ---- /dev/null -+++ b/t/t4139-apply-escape.sh -@@ -0,0 +1,141 @@ -+#!/bin/sh -+ -+test_description='paths written by git-apply cannot escape the working tree' -+. ./test-lib.sh -+ -+# tests will try to write to ../foo, and we do not -+# want them to escape the trash directory when they -+# fail -+test_expect_success 'bump git repo one level down' ' -+ mkdir inside && -+ mv .git inside/ && -+ cd inside -+' -+ -+# $1 = name of file -+# $2 = current path to file (if different) -+mkpatch_add () { -+ rm -f "${2:-$1}" && -+ cat <<-EOF -+ diff --git a/$1 b/$1 -+ new file mode 100644 -+ index 0000000..53c74cd -+ --- /dev/null -+ +++ b/$1 -+ @@ -0,0 +1 @@ -+ +evil -+ EOF -+} -+ -+mkpatch_del () { -+ echo evil >"${2:-$1}" && -+ cat <<-EOF -+ diff --git a/$1 b/$1 -+ deleted file mode 100644 -+ index 53c74cd..0000000 -+ --- a/$1 -+ +++ /dev/null -+ @@ -1 +0,0 @@ -+ -evil -+ EOF -+} -+ -+# $1 = name of file -+# $2 = content of symlink -+mkpatch_symlink () { -+ rm -f "$1" && -+ cat <<-EOF -+ diff --git a/$1 b/$1 -+ new file mode 120000 -+ index 0000000..$(printf "%s" "$2" | git hash-object --stdin) -+ --- /dev/null -+ +++ b/$1 -+ @@ -0,0 +1 @@ -+ +$2 -+ \ No newline at end of file -+ EOF -+} -+ -+test_expect_success 'cannot create file containing ..' ' -+ mkpatch_add ../foo >patch && -+ test_must_fail git apply patch && -+ test_path_is_missing ../foo -+' -+ -+test_expect_success 'can create file containing .. with --unsafe-paths' ' -+ mkpatch_add ../foo >patch && -+ git apply --unsafe-paths patch && -+ test_path_is_file ../foo -+' -+ -+test_expect_success 'cannot create file containing .. (index)' ' -+ mkpatch_add ../foo >patch && -+ test_must_fail git apply --index patch && -+ test_path_is_missing ../foo -+' -+ -+test_expect_success 'cannot create file containing .. with --unsafe-paths (index)' ' -+ mkpatch_add ../foo >patch && -+ test_must_fail git apply --index --unsafe-paths patch && -+ test_path_is_missing ../foo -+' -+ -+test_expect_success 'cannot delete file containing ..' ' -+ mkpatch_del ../foo >patch && -+ test_must_fail git apply patch && -+ test_path_is_file ../foo -+' -+ -+test_expect_success 'can delete file containing .. with --unsafe-paths' ' -+ mkpatch_del ../foo >patch && -+ git apply --unsafe-paths patch && -+ test_path_is_missing ../foo -+' -+ -+test_expect_success 'cannot delete file containing .. (index)' ' -+ mkpatch_del ../foo >patch && -+ test_must_fail git apply --index patch && -+ test_path_is_file ../foo -+' -+ -+test_expect_success SYMLINKS 'symlink escape via ..' ' -+ { -+ mkpatch_symlink tmp .. && -+ mkpatch_add tmp/foo ../foo -+ } >patch && -+ test_must_fail git apply patch && -+ test_path_is_missing tmp && -+ test_path_is_missing ../foo -+' -+ -+test_expect_success SYMLINKS 'symlink escape via .. (index)' ' -+ { -+ mkpatch_symlink tmp .. && -+ mkpatch_add tmp/foo ../foo -+ } >patch && -+ test_must_fail git apply --index patch && -+ test_path_is_missing tmp && -+ test_path_is_missing ../foo -+' -+ -+test_expect_success SYMLINKS 'symlink escape via absolute path' ' -+ { -+ mkpatch_symlink tmp "$(pwd)" && -+ mkpatch_add tmp/foo ../foo -+ } >patch && -+ test_must_fail git apply patch && -+ test_path_is_missing tmp && -+ test_path_is_missing ../foo -+' -+ -+test_expect_success SYMLINKS 'symlink escape via absolute path (index)' ' -+ { -+ mkpatch_symlink tmp "$(pwd)" && -+ mkpatch_add tmp/foo ../foo -+ } >patch && -+ test_must_fail git apply --index patch && -+ test_path_is_missing tmp && -+ test_path_is_missing ../foo -+' -+ -+test_done -diff --git a/t/t7415-submodule-names.sh b/t/t7415-submodule-names.sh -new file mode 100755 -index 0000000..6456d5a ---- /dev/null -+++ b/t/t7415-submodule-names.sh -@@ -0,0 +1,154 @@ -+#!/bin/sh -+ -+test_description='check handling of .. in submodule names -+ -+Exercise the name-checking function on a variety of names, and then give a -+real-world setup that confirms we catch this in practice. -+' -+. ./test-lib.sh -+. "$TEST_DIRECTORY"/lib-pack.sh -+ -+test_expect_success 'check names' ' -+ cat >expect <<-\EOF && -+ valid -+ valid/with/paths -+ EOF -+ -+ git submodule--helper check-name >actual <<-\EOF && -+ valid -+ valid/with/paths -+ -+ ../foo -+ /../foo -+ ..\foo -+ \..\foo -+ foo/.. -+ foo/../ -+ foo\.. -+ foo\..\ -+ foo/../bar -+ EOF -+ -+ test_cmp expect actual -+' -+ -+test_expect_success 'create innocent subrepo' ' -+ git init innocent && -+ ( cd innocent && git commit --allow-empty -m foo ) -+' -+ -+test_expect_success 'submodule add refuses invalid names' ' -+ test_must_fail \ -+ git submodule add --name ../../modules/evil "$PWD/innocent" evil -+' -+ -+test_expect_success 'add evil submodule' ' -+ git submodule add "$PWD/innocent" evil && -+ -+ mkdir modules && -+ cp -r .git/modules/evil modules && -+ write_script modules/evil/hooks/post-checkout <<-\EOF && -+ echo >&2 "RUNNING POST CHECKOUT" -+ EOF -+ -+ git config -f .gitmodules submodule.evil.update checkout && -+ git config -f .gitmodules --rename-section \ -+ submodule.evil submodule.../../modules/evil && -+ git add modules && -+ git commit -am evil -+' -+ -+# This step seems like it shouldn't be necessary, since the payload is -+# contained entirely in the evil submodule. But due to the vagaries of the -+# submodule code, checking out the evil module will fail unless ".git/modules" -+# exists. Adding another submodule (with a name that sorts before "evil") is an -+# easy way to make sure this is the case in the victim clone. -+test_expect_success 'add other submodule' ' -+ git submodule add "$PWD/innocent" another-module && -+ git add another-module && -+ git commit -am another -+' -+ -+test_expect_success 'clone evil superproject' ' -+ test_might_fail git clone --recurse-submodules . victim >output 2>&1 && -+ cat output && -+ ! grep "RUNNING POST CHECKOUT" output -+' -+ -+test_expect_success 'fsck detects evil superproject' ' -+ test_must_fail git fsck -+' -+ -+test_expect_success 'transfer.fsckObjects detects evil superproject (unpack)' ' -+ rm -rf dst.git && -+ git init --bare dst.git && -+ ( cd dst.git && git config transfer.fsckObjects true ) && -+ test_must_fail git push dst.git HEAD -+' -+ -+test_expect_success 'transfer.fsckObjects detects evil superproject (index)' ' -+ rm -rf dst.git && -+ git init --bare dst.git && -+ ( cd dst.git && git config transfer.fsckObjects true && -+ git config transfer.unpackLimit 1 ) && -+ test_must_fail git push dst.git HEAD -+' -+ -+# Normally our packs contain commits followed by trees followed by blobs. This -+# reverses the order, which requires backtracking to find the context of a -+# blob. We'll start with a fresh gitmodules-only tree to make it simpler. -+test_expect_success 'create oddly ordered pack' ' -+ git checkout --orphan odd && -+ git rm -rf --cached . && -+ git add .gitmodules && -+ git commit -m odd && -+ { -+ pack_header 3 && -+ pack_obj $(git rev-parse HEAD:.gitmodules) && -+ pack_obj $(git rev-parse HEAD^{tree}) && -+ pack_obj $(git rev-parse HEAD) -+ } >odd.pack && -+ pack_trailer odd.pack -+' -+ -+test_expect_success 'transfer.fsckObjects handles odd pack (unpack)' ' -+ rm -rf dst.git && -+ git init --bare dst.git && -+ ( cd dst.git && test_must_fail git unpack-objects --strict ) output && -+ test_i18ngrep "is a symbolic link" output -+ ) -+' -+ -+test_done -diff --git a/test-hashmap.c b/test-hashmap.c -new file mode 100644 -index 0000000..1a7ac2c ---- /dev/null -+++ b/test-hashmap.c -@@ -0,0 +1,335 @@ -+#include "cache.h" -+#include "hashmap.h" -+#include -+ -+struct test_entry -+{ -+ struct hashmap_entry ent; -+ /* key and value as two \0-terminated strings */ -+ char key[FLEX_ARRAY]; -+}; -+ -+static const char *get_value(const struct test_entry *e) -+{ -+ return e->key + strlen(e->key) + 1; -+} -+ -+static int test_entry_cmp(const struct test_entry *e1, -+ const struct test_entry *e2, const char* key) -+{ -+ return strcmp(e1->key, key ? key : e2->key); -+} -+ -+static int test_entry_cmp_icase(const struct test_entry *e1, -+ const struct test_entry *e2, const char* key) -+{ -+ return strcasecmp(e1->key, key ? key : e2->key); -+} -+ -+static struct test_entry *alloc_test_entry(int hash, char *key, int klen, -+ char *value, int vlen) -+{ -+ struct test_entry *entry = malloc(sizeof(struct test_entry) + klen -+ + vlen + 2); -+ hashmap_entry_init(entry, hash); -+ memcpy(entry->key, key, klen + 1); -+ memcpy(entry->key + klen + 1, value, vlen + 1); -+ return entry; -+} -+ -+#define HASH_METHOD_FNV 0 -+#define HASH_METHOD_I 1 -+#define HASH_METHOD_IDIV10 2 -+#define HASH_METHOD_0 3 -+#define HASH_METHOD_X2 4 -+#define TEST_SPARSE 8 -+#define TEST_ADD 16 -+#define TEST_SIZE 100000 -+ -+static unsigned int hash(unsigned int method, unsigned int i, const char *key) -+{ -+ unsigned int hash; -+ switch (method & 3) -+ { -+ case HASH_METHOD_FNV: -+ hash = strhash(key); -+ break; -+ case HASH_METHOD_I: -+ hash = i; -+ break; -+ case HASH_METHOD_IDIV10: -+ hash = i / 10; -+ break; -+ case HASH_METHOD_0: -+ hash = 0; -+ break; -+ } -+ -+ if (method & HASH_METHOD_X2) -+ hash = 2 * hash; -+ return hash; -+} -+ -+/* -+ * Test performance of hashmap.[ch] -+ * Usage: time echo "perfhashmap method rounds" | test-hashmap -+ */ -+static void perf_hashmap(unsigned int method, unsigned int rounds) -+{ -+ struct hashmap map; -+ char buf[16]; -+ struct test_entry **entries; -+ unsigned int *hashes; -+ unsigned int i, j; -+ -+ entries = malloc(TEST_SIZE * sizeof(struct test_entry *)); -+ hashes = malloc(TEST_SIZE * sizeof(int)); -+ for (i = 0; i < TEST_SIZE; i++) { -+ snprintf(buf, sizeof(buf), "%i", i); -+ entries[i] = alloc_test_entry(0, buf, strlen(buf), "", 0); -+ hashes[i] = hash(method, i, entries[i]->key); -+ } -+ -+ if (method & TEST_ADD) { -+ /* test adding to the map */ -+ for (j = 0; j < rounds; j++) { -+ hashmap_init(&map, (hashmap_cmp_fn) test_entry_cmp, 0); -+ -+ /* add entries */ -+ for (i = 0; i < TEST_SIZE; i++) { -+ hashmap_entry_init(entries[i], hashes[i]); -+ hashmap_add(&map, entries[i]); -+ } -+ -+ hashmap_free(&map, 0); -+ } -+ } else { -+ /* test map lookups */ -+ hashmap_init(&map, (hashmap_cmp_fn) test_entry_cmp, 0); -+ -+ /* fill the map (sparsely if specified) */ -+ j = (method & TEST_SPARSE) ? TEST_SIZE / 10 : TEST_SIZE; -+ for (i = 0; i < j; i++) { -+ hashmap_entry_init(entries[i], hashes[i]); -+ hashmap_add(&map, entries[i]); -+ } -+ -+ for (j = 0; j < rounds; j++) { -+ for (i = 0; i < TEST_SIZE; i++) { -+ hashmap_get_from_hash(&map, hashes[i], -+ entries[i]->key); -+ } -+ } -+ -+ hashmap_free(&map, 0); -+ } -+} -+ -+struct hash_entry -+{ -+ struct hash_entry *next; -+ char key[FLEX_ARRAY]; -+}; -+ -+/* -+ * Test performance of hash.[ch] -+ * Usage: time echo "perfhash method rounds" | test-hashmap -+ */ -+static void perf_hash(unsigned int method, unsigned int rounds) -+{ -+ struct hash_table map; -+ char buf[16]; -+ struct hash_entry **entries, **res, *entry; -+ unsigned int *hashes; -+ unsigned int i, j; -+ -+ entries = malloc(TEST_SIZE * sizeof(struct hash_entry *)); -+ hashes = malloc(TEST_SIZE * sizeof(int)); -+ for (i = 0; i < TEST_SIZE; i++) { -+ snprintf(buf, sizeof(buf), "%i", i); -+ entries[i] = malloc(sizeof(struct hash_entry) + strlen(buf) + 1); -+ strcpy(entries[i]->key, buf); -+ hashes[i] = hash(method, i, entries[i]->key); -+ } -+ -+ if (method & TEST_ADD) { -+ /* test adding to the map */ -+ for (j = 0; j < rounds; j++) { -+ init_hash(&map); -+ -+ /* add entries */ -+ for (i = 0; i < TEST_SIZE; i++) { -+ res = (struct hash_entry **) insert_hash( -+ hashes[i], entries[i], &map); -+ if (res) { -+ entries[i]->next = *res; -+ *res = entries[i]; -+ } else { -+ entries[i]->next = NULL; -+ } -+ } -+ -+ free_hash(&map); -+ } -+ } else { -+ /* test map lookups */ -+ init_hash(&map); -+ -+ /* fill the map (sparsely if specified) */ -+ j = (method & TEST_SPARSE) ? TEST_SIZE / 10 : TEST_SIZE; -+ for (i = 0; i < j; i++) { -+ res = (struct hash_entry **) insert_hash(hashes[i], -+ entries[i], &map); -+ if (res) { -+ entries[i]->next = *res; -+ *res = entries[i]; -+ } else { -+ entries[i]->next = NULL; -+ } -+ } -+ -+ for (j = 0; j < rounds; j++) { -+ for (i = 0; i < TEST_SIZE; i++) { -+ entry = lookup_hash(hashes[i], &map); -+ while (entry) { -+ if (!strcmp(entries[i]->key, entry->key)) -+ break; -+ entry = entry->next; -+ } -+ } -+ } -+ -+ free_hash(&map); -+ -+ } -+} -+ -+#define DELIM " \t\r\n" -+ -+/* -+ * Read stdin line by line and print result of commands to stdout: -+ * -+ * hash key -> strhash(key) memhash(key) strihash(key) memihash(key) -+ * put key value -> NULL / old value -+ * get key -> NULL / value -+ * remove key -> NULL / old value -+ * iterate -> key1 value1\nkey2 value2\n... -+ * size -> tablesize numentries -+ * -+ * perfhashmap method rounds -> test hashmap.[ch] performance -+ * perfhash method rounds -> test hash.[ch] performance -+ */ -+int main(int argc, char *argv[]) -+{ -+ char line[1024]; -+ struct hashmap map; -+ int icase; -+ -+ /* init hash map */ -+ icase = argc > 1 && !strcmp("ignorecase", argv[1]); -+ hashmap_init(&map, (hashmap_cmp_fn) (icase ? test_entry_cmp_icase -+ : test_entry_cmp), 0); -+ -+ /* process commands from stdin */ -+ while (fgets(line, sizeof(line), stdin)) { -+ char *cmd, *p1 = NULL, *p2 = NULL; -+ int l1 = 0, l2 = 0, hash = 0; -+ struct test_entry *entry; -+ -+ /* break line into command and up to two parameters */ -+ cmd = strtok(line, DELIM); -+ /* ignore empty lines */ -+ if (!cmd || *cmd == '#') -+ continue; -+ -+ p1 = strtok(NULL, DELIM); -+ if (p1) { -+ l1 = strlen(p1); -+ hash = icase ? strihash(p1) : strhash(p1); -+ p2 = strtok(NULL, DELIM); -+ if (p2) -+ l2 = strlen(p2); -+ } -+ -+ if (!strcmp("hash", cmd) && l1) { -+ -+ /* print results of different hash functions */ -+ printf("%u %u %u %u\n", strhash(p1), memhash(p1, l1), -+ strihash(p1), memihash(p1, l1)); -+ -+ } else if (!strcmp("add", cmd) && l1 && l2) { -+ -+ /* create entry with key = p1, value = p2 */ -+ entry = alloc_test_entry(hash, p1, l1, p2, l2); -+ -+ /* add to hashmap */ -+ hashmap_add(&map, entry); -+ -+ } else if (!strcmp("put", cmd) && l1 && l2) { -+ -+ /* create entry with key = p1, value = p2 */ -+ entry = alloc_test_entry(hash, p1, l1, p2, l2); -+ -+ /* add / replace entry */ -+ entry = hashmap_put(&map, entry); -+ -+ /* print and free replaced entry, if any */ -+ puts(entry ? get_value(entry) : "NULL"); -+ free(entry); -+ -+ } else if (!strcmp("get", cmd) && l1) { -+ -+ /* lookup entry in hashmap */ -+ entry = hashmap_get_from_hash(&map, hash, p1); -+ -+ /* print result */ -+ if (!entry) -+ puts("NULL"); -+ while (entry) { -+ puts(get_value(entry)); -+ entry = hashmap_get_next(&map, entry); -+ } -+ -+ } else if (!strcmp("remove", cmd) && l1) { -+ -+ /* setup static key */ -+ struct hashmap_entry key; -+ hashmap_entry_init(&key, hash); -+ -+ /* remove entry from hashmap */ -+ entry = hashmap_remove(&map, &key, p1); -+ -+ /* print result and free entry*/ -+ puts(entry ? get_value(entry) : "NULL"); -+ free(entry); -+ -+ } else if (!strcmp("iterate", cmd)) { -+ -+ struct hashmap_iter iter; -+ hashmap_iter_init(&map, &iter); -+ while ((entry = hashmap_iter_next(&iter))) -+ printf("%s %s\n", entry->key, get_value(entry)); -+ -+ } else if (!strcmp("size", cmd)) { -+ -+ /* print table sizes */ -+ printf("%u %u\n", map.tablesize, map.size); -+ -+ } else if (!strcmp("perfhashmap", cmd) && l1 && l2) { -+ -+ perf_hashmap(atoi(p1), atoi(p2)); -+ -+ } else if (!strcmp("perfhash", cmd) && l1 && l2) { -+ -+ perf_hash(atoi(p1), atoi(p2)); -+ -+ } else { -+ -+ printf("Unknown command %s\n", cmd); -+ -+ } -+ } -+ -+ hashmap_free(&map, 1); -+ return 0; -+} --- -2.14.4 - diff --git a/git-cve-2018-17456-tests.patch b/git-cve-2018-17456-tests.patch deleted file mode 100644 index e365cf2b0c890979b2f208a49c50f694a0f9588e..0000000000000000000000000000000000000000 --- a/git-cve-2018-17456-tests.patch +++ /dev/null @@ -1,164 +0,0 @@ -From 90243e8192574f43bb84be01528504ec230d7fd3 Mon Sep 17 00:00:00 2001 -From: Pavel Cahyna -Date: Fri, 19 Oct 2018 10:55:43 +0200 -Subject: [PATCH 2/2] submodule-config: ban submodule urls that start with dash - - tests - -Our tests cover two cases: - - 1. A file url with "./" continues to work, showing that - there's an escape hatch for people with truly silly - repo names. - - 2. A url starting with "-" is rejected. - -Note that we expect case (2) to fail, but it would have done -so even without this commit, for the reasons given above. -So instead of just expecting failure, let's also check for -the magic word "ignoring" on stderr. That lets us know that -we failed for the right reason. - -[pc: backported to 1.8.3.1 by avoiding -C in tests] - -submodule-config: ban submodule paths that start with a dash - test - -There are two minor differences to the tests in t7416 (that -covered urls): - - 1. We don't have a "./-sub" escape hatch to make this - work, since the submodule code expects to be able to - match canonical index names to the path field (so you - are free to add submodule config with that path, but we - would never actually use it, since an index entry would - never start with "./"). - - 2. After this patch, cloning actually succeeds. Since we - ignore the submodule.*.path value, we fail to find a - config stanza for our submodule at all, and simply - treat it as inactive. We still check for the "ignoring" - message. - -[jn: - - the original patch expects 'git clone' to succeed in - the test because v2.13.0-rc0~10^2~3 (clone: teach - --recurse-submodules to optionally take a pathspec, - 2017-03-17) makes 'git clone' skip invalid submodules. - Updated the test to pass in older Git versions where the - submodule name check makes 'git clone' fail.] - -[pc: - - avoid -C in tests - - reimplement git mv of a submodule, git mv gained that functionality later.] - -fsck: detect submodule urls starting with dash - tests - -[pc: backported to 1.8.3.1 by avoiding -C in tests ] - -fsck: detect submodule paths starting with dash - test - -commit 1a7fd1fb2998002da6e9ff2ee46e1bdd25ee8404 upstream. - -[pc: backported to 1.8.3.1 by avoiding -C in tests ] ---- - t/t7416-submodule-dash-url.sh | 49 +++++++++++++++++++++++++++++++++++++++++++ - t/t7417-submodule-path-url.sh | 32 ++++++++++++++++++++++++++++ - 2 files changed, 81 insertions(+) - create mode 100755 t/t7416-submodule-dash-url.sh - create mode 100755 t/t7417-submodule-path-url.sh - -diff --git a/t/t7416-submodule-dash-url.sh b/t/t7416-submodule-dash-url.sh -new file mode 100755 -index 000000000..e85f2e9d2 ---- /dev/null -+++ b/t/t7416-submodule-dash-url.sh -@@ -0,0 +1,49 @@ -+#!/bin/sh -+ -+test_description='check handling of .gitmodule url with dash' -+. ./test-lib.sh -+ -+test_expect_success 'create submodule with protected dash in url' ' -+ git init upstream && -+ ( cd upstream && git commit --allow-empty -m base ) && -+ mv upstream ./-upstream && -+ git submodule add ./-upstream sub && -+ git add sub .gitmodules && -+ git commit -m submodule -+' -+ -+test_expect_success 'clone can recurse submodule' ' -+ test_when_finished "rm -rf dst" && -+ git clone --recurse-submodules . dst && -+ echo base >expect && -+ ( cd dst/sub && git log -1 --format=%s ) >actual && -+ test_cmp expect actual -+' -+ -+test_expect_success 'fsck accepts protected dash' ' -+ test_when_finished "rm -rf dst" && -+ git init --bare dst && -+ ( cd dst && git config transfer.fsckObjects true ) && -+ git push dst HEAD -+' -+ -+test_expect_success 'remove ./ protection from .gitmodules url' ' -+ perl -i -pe "s{\./}{}" .gitmodules && -+ git commit -am "drop protection" -+' -+ -+test_expect_success 'clone rejects unprotected dash' ' -+ test_when_finished "rm -rf dst" && -+ test_must_fail git clone --recurse-submodules . dst 2>err && -+ test_i18ngrep "may be interpreted as a command-line option" err -+' -+ -+test_expect_success 'fsck rejects unprotected dash' ' -+ test_when_finished "rm -rf dst" && -+ git init --bare dst && -+ ( cd dst && git config transfer.fsckObjects true ) && -+ test_must_fail git push dst HEAD 2>err && -+ test_i18ngrep "disallowed submodule url" err -+' -+ -+test_done -diff --git a/t/t7417-submodule-path-url.sh b/t/t7417-submodule-path-url.sh -new file mode 100755 -index 000000000..141b42a11 ---- /dev/null -+++ b/t/t7417-submodule-path-url.sh -@@ -0,0 +1,32 @@ -+#!/bin/sh -+ -+test_description='check handling of .gitmodule path with dash' -+. ./test-lib.sh -+ -+test_expect_success 'create submodule with dash in path' ' -+ git init upstream && -+ ( cd upstream && git commit --allow-empty -m base ) && -+ git submodule add ./upstream sub && -+ mv -- sub -sub && -+ git rm --cached sub && -+ sed -i -e "/=.*sub$/s/sub/-sub/" .git/modules/sub/config && -+ sed -i -e "/=.*sub$/s/sub/-sub/" .gitmodules && -+ git add -- -sub .git/modules/sub/config .gitmodules && -+ git commit -m submodule -+' -+ -+test_expect_success 'clone rejects unprotected dash' ' -+ test_when_finished "rm -rf dst" && -+ test_might_fail git clone --recurse-submodules . dst 2>err && -+ test_i18ngrep "may be interpreted as a command-line option" err -+' -+ -+test_expect_success 'fsck rejects unprotected dash' ' -+ test_when_finished "rm -rf dst" && -+ git init --bare dst && -+ ( cd dst && git config transfer.fsckObjects true ) && -+ test_must_fail git push dst HEAD 2>err && -+ test_i18ngrep "disallowed submodule path" err -+' -+ -+test_done --- -2.14.4 - diff --git a/git-cve-2018-17456.patch b/git-cve-2018-17456.patch deleted file mode 100644 index 0f595bbf976007866debc07962f87591b7314c40..0000000000000000000000000000000000000000 --- a/git-cve-2018-17456.patch +++ /dev/null @@ -1,254 +0,0 @@ -From d819a25360ba38dfec31e37413963adf5688db80 Mon Sep 17 00:00:00 2001 -From: Jeff King -Date: Mon, 24 Sep 2018 04:32:15 -0400 -Subject: [PATCH 1/2] submodule--helper: use "--" to signal end of clone - options - -commit 98afac7a7cefdca0d2c4917dd8066a59f7088265 upstream. - -When we clone a submodule, we call "git clone $url $path". -But there's nothing to say that those components can't begin -with a dash themselves, confusing git-clone into thinking -they're options. Let's pass "--" to make it clear what we -expect. - -There's no test here, because it's actually quite hard to -make these names work, even with "git clone" parsing them -correctly. And we're going to restrict these cases even -further in future commits. So we'll leave off testing until -then; this is just the minimal fix to prevent us from doing -something stupid with a badly formed entry. - -[jn: backported to 2.1.y by applying to git-submodule.sh - instead of submodule--helper] - -Reported-by: joernchen -Signed-off-by: Jeff King -Signed-off-by: Junio C Hamano -Signed-off-by: Jonathan Nieder - -submodule-config: ban submodule urls that start with dash - -commit f6adec4e329ef0e25e14c63b735a5956dc67b8bc upstream. - -The previous commit taught the submodule code to invoke our -"git clone $url $path" with a "--" separator so that we -aren't confused by urls or paths that start with dashes. - -However, that's just one code path. It's not clear if there -are others, and it would be an easy mistake to add one in -the future. Moreover, even with the fix in the previous -commit, it's quite hard to actually do anything useful with -such an entry. Any url starting with a dash must fall into -one of three categories: - - - it's meant as a file url, like "-path". But then any - clone is not going to have the matching path, since it's - by definition relative inside the newly created clone. If - you spell it as "./-path", the submodule code sees the - "/" and translates this to an absolute path, so it at - least works (assuming the receiver has the same - filesystem layout as you). But that trick does not apply - for a bare "-path". - - - it's meant as an ssh url, like "-host:path". But this - already doesn't work, as we explicitly disallow ssh - hostnames that begin with a dash (to avoid option - injection against ssh). - - - it's a remote-helper scheme, like "-scheme::data". This - _could_ work if the receiver bends over backwards and - creates a funny-named helper like "git-remote--scheme". - But normally there would not be any helper that matches. - -Since such a url does not work today and is not likely to do -anything useful in the future, let's simply disallow them -entirely. That protects the existing "git clone" path (in a -belt-and-suspenders way), along with any others that might -exist. - -[jn: backported to 2.1.y by porting to shell] -[pc: backported to 1.8.3.1 by using $sm_path instead of $displayname - and split tests into a separate commit] - -Signed-off-by: Jeff King -Signed-off-by: Junio C Hamano -Signed-off-by: Jonathan Nieder - -submodule-config: ban submodule paths that start with a dash - -commit 273c61496f88c6495b886acb1041fe57965151da upstream. - -We recently banned submodule urls that look like -command-line options. This is the matching change to ban -leading-dash paths. - -As with the urls, this should not break any use cases that -currently work. Even with our "--" separator passed to -git-clone, git-submodule.sh gets confused. Without the code -portion of this patch, the clone of "-sub" added in t7417 -would yield results like: - - /path/to/git-submodule: 410: cd: Illegal option -s - /path/to/git-submodule: 417: cd: Illegal option -s - /path/to/git-submodule: 410: cd: Illegal option -s - /path/to/git-submodule: 417: cd: Illegal option -s - Fetched in submodule path '-sub', but it did not contain b56243f8f4eb91b2f1f8109452e659f14dd3fbe4. D -irect fetching of that commit failed. - -Moreover, naively adding such a submodule doesn't work: - - $ git submodule add $url -sub - The following path is ignored by one of your .gitignore files: - -sub - -even though there is no such ignore pattern (the test script -hacks around this with a well-placed "git mv"). - -Unlike leading-dash urls, though, it's possible that such a -path _could_ be useful if we eventually made it work. So -this commit should be seen not as recommending a particular -policy, but rather temporarily closing off a broken and -possibly dangerous code-path. We may revisit this decision -later. - -[jn: ported to git-submodule.sh - pc: split the test into a separate commit ] - -fsck: detect submodule urls starting with dash - -commit a124133e1e6ab5c7a9fef6d0e6bcb084e3455b46 upstream. - -Urls with leading dashes can cause mischief on older -versions of Git. We should detect them so that they can be -rejected by receive.fsckObjects, preventing modern versions -of git from being a vector by which attacks can spread. - -[jn: backported to 2.1.y: using error_func instead of report - to report fsck errors] - -[pc: split tests into a separate commit] - -Signed-off-by: Jeff King -Signed-off-by: Junio C Hamano -Signed-off-by: Jonathan Nieder - -fsck: detect submodule paths starting with dash - -commit 1a7fd1fb2998002da6e9ff2ee46e1bdd25ee8404 upstream. - -As with urls, submodule paths with dashes are ignored by -git, but may end up confusing older versions. Detecting them -via fsck lets us prevent modern versions of git from being a -vector to spread broken .gitmodules to older versions. - -Compared to blocking leading-dash urls, though, this -detection may be less of a good idea: - - 1. While such paths provide confusing and broken results, - they don't seem to actually work as option injections - against anything except "cd". In particular, the - submodule code seems to canonicalize to an absolute - path before running "git clone" (so it passes - /your/clone/-sub). - - 2. It's more likely that we may one day make such names - actually work correctly. Even after we revert this fsck - check, it will continue to be a hassle until hosting - servers are all updated. - -On the other hand, it's not entirely clear that the behavior -in older versions is safe. And if we do want to eventually -allow this, we may end up doing so with a special syntax -anyway (e.g., writing "./-sub" in the .gitmodules file, and -teaching the submodule code to canonicalize it when -comparing). - -So on balance, this is probably a good protection. - -[jn: backported to 2.1.y: using error_func instead of report - to report fsck errors] - -[pc: split test to a separate commit] ---- - fsck.c | 10 ++++++++++ - git-submodule.sh | 20 +++++++++++++++----- - 2 files changed, 25 insertions(+), 5 deletions(-) - -diff --git a/fsck.c b/fsck.c -index 811724125..90d641066 100644 ---- a/fsck.c -+++ b/fsck.c -@@ -442,6 +442,16 @@ static int fsck_gitmodules_fn(const char *var, const char *value, void *vdata) - data->ret += data->error_func(data->obj, FSCK_ERROR, - "disallowed submodule name: %s", - name); -+ if (!strcmp(key, "url") && value && -+ looks_like_command_line_option(value)) -+ data->ret += data->error_func(data->obj, FSCK_ERROR, -+ "disallowed submodule url: %s", -+ value); -+ if (!strcmp(key, "path") && value && -+ looks_like_command_line_option(value)) -+ data->ret += data->error_func(data->obj, FSCK_ERROR, -+ "disallowed submodule path: %s", -+ value); - free(name); - - return 0; -diff --git a/git-submodule.sh b/git-submodule.sh -index e958ce840..b5176ecc3 100755 ---- a/git-submodule.sh -+++ b/git-submodule.sh -@@ -205,6 +205,11 @@ module_name() - re=$(printf '%s\n' "$1" | sed -e 's/[].[^$\\*]/\\&/g') - name=$( git config -f .gitmodules --get-regexp '^submodule\..*\.path$' | - sed -n -e 's|^submodule\.\(.*\)\.path '"$re"'$|\1|p' ) -+ case "$sm_path" in -+ -*) -+ die "$(eval_gettext "Submodule path '\$sm_path' may be interpreted as a command-line option")" -+ ;; -+ esac - test -z "$name" && - die "$(eval_gettext "No submodule mapping found in .gitmodules for path '\$sm_path'")" - check_module_name "$name" -@@ -248,7 +253,7 @@ module_clone() - ( - clear_local_git_env - git clone $quiet -n ${reference:+"$reference"} \ -- --separate-git-dir "$gitdir" "$url" "$sm_path" -+ --separate-git-dir "$gitdir" -- "$url" "$sm_path" - ) || - die "$(eval_gettext "Clone of '\$url' into submodule path '\$sm_path' failed")" - fi -@@ -547,11 +552,13 @@ cmd_init() - if test -z "$(git config "submodule.$name.url")" - then - url=$(git config -f .gitmodules submodule."$name".url) -- test -z "$url" && -- die "$(eval_gettext "No url found for submodule path '\$sm_path' in .gitmodules")" -- -- # Possibly a url relative to parent - case "$url" in -+ "") -+ die "$(eval_gettext "No url found for submodule path '\$sm_path' in .gitmodules")" -+ ;; -+ -*) -+ die "$(eval_gettext "Submodule at path '\$sm_path' has url '\$url' which may be interpreted as a command-line option")" -+ ;; - ./*|../*) - url=$(resolve_relative_url "$url") || exit - ;; -@@ -1213,6 +1220,9 @@ cmd_sync() - - # Possibly a url relative to parent - case "$url" in -+ -*) -+ die "$(eval_gettext "Submodule at path '\$sm_path' has url '\$url' which may be interpreted as a command-line option")" -+ ;; - ./*|../*) - # rewrite foo/bar as ../.. to find path from - # submodule work tree to superproject work tree --- -2.14.4 - diff --git a/git-cve-2019-1387.patch b/git-cve-2019-1387.patch deleted file mode 100644 index 20a5bfed4506d6e4921b33982a6314483b560d16..0000000000000000000000000000000000000000 --- a/git-cve-2019-1387.patch +++ /dev/null @@ -1,166 +0,0 @@ -diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c -index cc79d059f2..29adde60bd 100644 ---- a/builtin/submodule--helper.c -+++ b/builtin/submodule--helper.c -@@ -25,11 +25,32 @@ static int check_name(int argc, const char **argv, const char *prefix) - return 0; - } - -+/* -+ * Exit non-zero if the proposed submodule repository path is inside -+ * another submodules' git dir. -+ */ -+static int validate_git_dir(int argc, const char **argv, const char *prefix) -+{ -+ char *sm_gitdir; -+ -+ if (argc != 3) -+ usage("git submodule--helper validate-git-dir "); -+ sm_gitdir = xstrdup(argv[1]); -+ if (validate_submodule_git_dir(sm_gitdir, argv[2]) < 0) { -+ free(sm_gitdir); -+ return 1; -+ } -+ free(sm_gitdir); -+ return 0; -+} -+ - int cmd_submodule__helper(int argc, const char **argv, const char *prefix) - { - if (argc < 2) - usage("git submodule--helper "); - if (!strcmp(argv[1], "check-name")) - return check_name(argc - 1, argv + 1, prefix); -+ if (!strcmp(argv[1], "validate-git-dir")) -+ return validate_git_dir(argc - 1, argv + 1, prefix); - die(_("'%s' is not a valid submodule--helper subcommand"), argv[1]); - } -diff -ruNp a/git-submodule.sh b/git-submodule.sh ---- a/git-submodule.sh 2020-01-09 20:01:48.885647299 +0100 -+++ b/git-submodule.sh 2020-01-10 08:42:05.107514269 +0100 -@@ -253,6 +253,11 @@ module_clone() - gitdir_base="$gitdir/modules/$base_name" - gitdir="$gitdir/modules/$name" - -+ if ! git submodule--helper validate-git-dir "$gitdir" "$name" -+ then -+ die "$(eval_gettextln "refusing to create/use '\$gitdir' in another submodule's git dir")" -+ fi -+ - if test -d "$gitdir" - then - mkdir -p "$sm_path" - -diff --git a/git-submodule.sh b/git-submodule.sh -index ca16579c3c..bf5ffdb1f6 100755 ---- a/git-submodule.sh -+++ b/git-submodule.sh -@@ -419,6 +419,11 @@ Use -f if you really want to add it." >&2 - fi - - else -+ sm_gitdir=".git/modules/$sm_name" -+ if ! git submodule--helper validate-git-dir "$sm_gitdir" "$sm_name" -+ then -+ die "$(eval_gettextln "refusing to create/use '\$sm_gitdir' in another submodule's git dir")" -+ fi - if test -d ".git/modules/$sm_name" - then - if test -z "$force" -diff --git a/submodule.c b/submodule.c -index 6337cab091..9927f56a33 100644 ---- a/submodule.c -+++ b/submodule.c -@@ -1034,3 +1034,43 @@ int merge_submodule(unsigned char result[20], const char *path, - free(merges.objects); - return 0; - } -+int validate_submodule_git_dir(char *git_dir, const char *submodule_name) -+{ -+ size_t len = strlen(git_dir), suffix_len = strlen(submodule_name); -+ char *p; -+ int ret = 0; -+ -+ if (len <= suffix_len || (p = git_dir + len - suffix_len)[-1] != '/' || -+ strcmp(p, submodule_name)) -+ die("BUG: submodule name '%s' not a suffix of git dir '%s'", -+ submodule_name, git_dir); -+ -+ /* -+ * We prevent the contents of sibling submodules' git directories to -+ * clash. -+ * -+ * Example: having a submodule named `hippo` and another one named -+ * `hippo/hooks` would result in the git directories -+ * `.git/modules/hippo/` and `.git/modules/hippo/hooks/`, respectively, -+ * but the latter directory is already designated to contain the hooks -+ * of the former. -+ */ -+ for (; *p; p++) { -+ if (is_dir_sep(*p)) { -+ char c = *p; -+ -+ *p = '\0'; -+ if (is_git_directory(git_dir)) -+ ret = -1; -+ *p = c; -+ -+ if (ret < 0) -+ return error(_("submodule git dir '%s' is " -+ "inside git dir '%.*s'"), -+ git_dir, -+ (int)(p - git_dir), git_dir); -+ } -+ } -+ -+ return 0; -+} -diff --git a/submodule.h b/submodule.h -index 59dbdfbd17..9c99457a3f 100644 ---- a/submodule.h -+++ b/submodule.h -@@ -37,6 +37,11 @@ int find_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_nam - struct string_list *needs_pushing); - int push_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_name); - -+/* -+ * Make sure that no submodule's git dir is nested in a sibling submodule's. -+ */ -+int validate_submodule_git_dir(char *git_dir, const char *submodule_name); -+ - /* - * Returns 0 if the name is syntactically acceptable as a submodule "name" - * (e.g., that may be found in the subsection of a .gitmodules file) and -1 -diff --git a/t/t7415-submodule-names.sh b/t/t7415-submodule-names.sh -index 6456d5ae43..29393de617 100755 ---- a/t/t7415-submodule-names.sh -+++ b/t/t7415-submodule-names.sh -@@ -151,4 +151,27 @@ test_expect_success 'fsck detects symlinked .gitmodules file' ' - ) - ' - -+test_expect_success 'git dirs of sibling submodules must not be nested' ' -+ git init nested && -+ ( -+ cd nested && -+ test_commit nested && -+ cat >.gitmodules <<-EOF && -+ [submodule "hippo"] -+ url = . -+ path = thing1 -+ [submodule "hippo/hooks"] -+ url = . -+ path = thing2 -+ EOF -+ git clone . thing1 && -+ git clone . thing2 && -+ git add .gitmodules thing1 thing2 && -+ test_tick && -+ git commit -m nested -+ ) && -+ test_must_fail git clone --recurse-submodules nested clone 2>err && -+ test_i18ngrep "is inside git dir" err -+' -+ - test_done - diff --git a/git-cve-2020-5260.patch b/git-cve-2020-5260.patch deleted file mode 100644 index b594ed22d6e661b46bb8007526d7e2a4e46a4a62..0000000000000000000000000000000000000000 --- a/git-cve-2020-5260.patch +++ /dev/null @@ -1,26 +0,0 @@ -diff -ur b/credential.c a/credential.c ---- b/credential.c 2020-04-14 14:15:41.637223958 +0200 -+++ a/credential.c 2020-04-14 14:59:03.325862182 +0200 -@@ -190,6 +190,8 @@ - { - if (!value) - return; -+ if (strchr(value, '\n')) -+ die("credential value for %s contains newline", key); - fprintf(fp, "%s=%s\n", key, value); - } - -diff -ur b/t/t0300-credentials.sh a/t/t0300-credentials.sh ---- b/t/t0300-credentials.sh 2020-04-14 14:15:41.569223126 +0200 -+++ a/t/t0300-credentials.sh 2020-04-14 15:00:01.331571252 +0200 -@@ -289,4 +289,10 @@ - EOF - ' - -+test_expect_success 'url parser rejects embedded newlines' ' -+ test_must_fail git credential fill <<-\EOF -+ url=https://one.example.com?%0ahost=two.example.com/ -+ EOF -+' -+ - test_done diff --git a/git-cve-2022-23521.patch b/git-cve-2022-23521.patch deleted file mode 100644 index 8a93d086d5dbaf5d3cc4ccec82ddac6b7b6da598..0000000000000000000000000000000000000000 --- a/git-cve-2022-23521.patch +++ /dev/null @@ -1,571 +0,0 @@ -diff -ur b/attr.c c/attr.c ---- b/attr.c 2013-06-10 22:01:55.000000000 +0200 -+++ c/attr.c 2023-02-21 12:25:20.735892607 +0100 -@@ -12,6 +12,7 @@ - #include "exec_cmd.h" - #include "attr.h" - #include "dir.h" -+#include "utf8.h" - - const char git_attr__true[] = "(builtin)true"; - const char git_attr__false[] = "\0(builtin)false"; -@@ -55,26 +56,36 @@ - return val; - } - --static int invalid_attr_name(const char *name, int namelen) -+static int attr_name_valid(const char *name, size_t namelen) - { - /* - * Attribute name cannot begin with '-' and must consist of - * characters from [-A-Za-z0-9_.]. - */ - if (namelen <= 0 || *name == '-') -- return -1; -+ return 0; - while (namelen--) { - char ch = *name++; - if (! (ch == '-' || ch == '.' || ch == '_' || - ('0' <= ch && ch <= '9') || - ('a' <= ch && ch <= 'z') || - ('A' <= ch && ch <= 'Z')) ) -- return -1; -+ return 0; - } -- return 0; -+ return 1; - } - --static struct git_attr *git_attr_internal(const char *name, int len) -+static void report_invalid_attr(const char *name, size_t len, -+ const char *src, int lineno) -+{ -+ struct strbuf err = STRBUF_INIT; -+ strbuf_addf(&err, _("%.*s is not a valid attribute name"), -+ (int) len, name); -+ fprintf(stderr, "%s: %s:%d\n", err.buf, src, lineno); -+ strbuf_release(&err); -+} -+ -+static struct git_attr *git_attr_internal(const char *name, size_t len) - { - unsigned hval = hash_name(name, len); - unsigned pos = hval % HASHSIZE; -@@ -86,7 +97,7 @@ - return a; - } - -- if (invalid_attr_name(name, len)) -+ if (!attr_name_valid(name, len)) - return NULL; - - a = xmalloc(sizeof(*a) + len + 1); -@@ -142,7 +153,7 @@ - struct git_attr *attr; - } u; - char is_macro; -- unsigned num_attr; -+ size_t num_attr; - struct attr_state state[FLEX_ARRAY]; - }; - -@@ -159,7 +170,7 @@ - struct attr_state *e) - { - const char *ep, *equals; -- int len; -+ size_t len; - - ep = cp + strcspn(cp, blank); - equals = strchr(cp, '='); -@@ -174,10 +185,8 @@ - cp++; - len--; - } -- if (invalid_attr_name(cp, len)) { -- fprintf(stderr, -- "%.*s is not a valid attribute name: %s:%d\n", -- len, cp, src, lineno); -+ if (!attr_name_valid(cp, len)) { -+ report_invalid_attr(cp, len, src, lineno); - return NULL; - } - } else { -@@ -199,8 +208,7 @@ - static struct match_attr *parse_attr_line(const char *line, const char *src, - int lineno, int macro_ok) - { -- int namelen; -- int num_attr, i; -+ size_t namelen, num_attr, i; - const char *cp, *name, *states; - struct match_attr *res = NULL; - int is_macro; -@@ -209,6 +217,12 @@ - if (!*cp || *cp == '#') - return NULL; - name = cp; -+ -+ if (strlen(line) >= ATTR_MAX_LINE_LENGTH) { -+ warning(_("ignoring overly long attributes line %d"), lineno); -+ return NULL; -+ } -+ - namelen = strcspn(name, blank); - if (strlen(ATTRIBUTE_MACRO_PREFIX) < namelen && - !prefixcmp(name, ATTRIBUTE_MACRO_PREFIX)) { -@@ -221,10 +235,8 @@ - name += strlen(ATTRIBUTE_MACRO_PREFIX); - name += strspn(name, blank); - namelen = strcspn(name, blank); -- if (invalid_attr_name(name, namelen)) { -- fprintf(stderr, -- "%.*s is not a valid attribute name: %s:%d\n", -- namelen, name, src, lineno); -+ if (!attr_name_valid(name, namelen)) { -+ report_invalid_attr(cp, namelen, src, lineno); - return NULL; - } - } -@@ -241,10 +253,9 @@ - return NULL; - } - -- res = xcalloc(1, -- sizeof(*res) + -- sizeof(struct attr_state) * num_attr + -- (is_macro ? 0 : namelen + 1)); -+ res = xcalloc(1, st_add3(sizeof(*res), -+ st_mult(sizeof(struct attr_state), num_attr), -+ is_macro ? 0 : namelen + 1)); - if (is_macro) - res->u.attr = git_attr_internal(name, namelen); - else { -@@ -301,11 +312,11 @@ - - static void free_attr_elem(struct attr_stack *e) - { -- int i; -+ unsigned i; - free(e->origin); - for (i = 0; i < e->num_matches; i++) { - struct match_attr *a = e->attrs[i]; -- int j; -+ size_t j; - for (j = 0; j < a->num_attr; j++) { - const char *setto = a->state[j].setto; - if (setto == ATTR__TRUE || -@@ -364,20 +375,39 @@ - - static struct attr_stack *read_attr_from_file(const char *path, int macro_ok) - { -+ struct strbuf buf = STRBUF_INIT; - FILE *fp = fopen(path, "r"); - struct attr_stack *res; -- char buf[2048]; - int lineno = 0; -+ int fd; -+ struct stat st; - - if (!fp) { - if (errno != ENOENT && errno != ENOTDIR) - warn_on_inaccessible(path); - return NULL; - } -+ -+ fd = fileno(fp); -+ if (fstat(fd, &st)) { -+ warning_errno(_("cannot fstat gitattributes file '%s'"), path); -+ fclose(fp); -+ return NULL; -+ } -+ if (st.st_size >= ATTR_MAX_FILE_SIZE) { -+ warning(_("ignoring overly large gitattributes file '%s'"), path); -+ fclose(fp); -+ return NULL; -+ } -+ - res = xcalloc(1, sizeof(*res)); -- while (fgets(buf, sizeof(buf), fp)) -- handle_attr_line(res, buf, path, ++lineno, macro_ok); -+ while (strbuf_getline(&buf, fp, '\n') != EOF) { -+ if (!lineno && starts_with(buf.buf, utf8_bom)) -+ strbuf_remove(&buf, 0, strlen(utf8_bom)); -+ handle_attr_line(res, buf.buf, path, ++lineno, macro_ok); -+ } - fclose(fp); -+ strbuf_release(&buf); - return res; - } - -@@ -386,11 +416,18 @@ - struct attr_stack *res; - char *buf, *sp; - int lineno = 0; -+ unsigned long size; - -- buf = read_blob_data_from_index(use_index ? use_index : &the_index, path, NULL); -+ buf = read_blob_data_from_index(use_index ? use_index : &the_index, path, &size); - if (!buf) - return NULL; - -+ if (size >= ATTR_MAX_FILE_SIZE) { -+ warning(_("ignoring overly large gitattributes blob '%s'"), path); -+ return NULL; -+ } -+ -+ - res = xcalloc(1, sizeof(*res)); - for (sp = buf; *sp; ) { - char *ep; -@@ -648,15 +685,15 @@ - - static int macroexpand_one(int attr_nr, int rem); - --static int fill_one(const char *what, struct match_attr *a, int rem) -+static int fill_one(const char *what, const struct match_attr *a, int rem) - { - struct git_attr_check *check = check_all_attr; -- int i; -+ size_t i; - -- for (i = a->num_attr - 1; 0 < rem && 0 <= i; i--) { -- struct git_attr *attr = a->state[i].attr; -+ for (i = a->num_attr; rem > 0 && i > 0; i--) { -+ const struct git_attr *attr = a->state[i - 1].attr; - const char **n = &(check[attr->attr_nr].value); -- const char *v = a->state[i].setto; -+ const char *v = a->state[i - 1].setto; - - if (*n == ATTR__UNKNOWN) { - debug_set(what, -@@ -673,11 +710,11 @@ - static int fill(const char *path, int pathlen, int basename_offset, - struct attr_stack *stk, int rem) - { -- int i; -+ unsigned i; - const char *base = stk->origin ? stk->origin : ""; - -- for (i = stk->num_matches - 1; 0 < rem && 0 <= i; i--) { -- struct match_attr *a = stk->attrs[i]; -+ for (i = stk->num_matches; 0 < rem && 0 < i; i--) { -+ const struct match_attr *a = stk->attrs[i - 1]; - if (a->is_macro) - continue; - if (path_matches(path, pathlen, basename_offset, -@@ -691,14 +728,14 @@ - { - struct attr_stack *stk; - struct match_attr *a = NULL; -- int i; -+ unsigned i; - - if (check_all_attr[attr_nr].value != ATTR__TRUE) - return rem; - - for (stk = attr_stack; !a && stk; stk = stk->prev) -- for (i = stk->num_matches - 1; !a && 0 <= i; i--) { -- struct match_attr *ma = stk->attrs[i]; -+ for (i = stk->num_matches; !a && i > 0; i--) { -+ struct match_attr *ma = stk->attrs[i - 1]; - if (!ma->is_macro) - continue; - if (ma->u.attr->attr_nr == attr_nr) -diff -ur b/attr.h c/attr.h ---- b/attr.h 2013-06-10 22:01:55.000000000 +0200 -+++ c/attr.h 2023-02-21 12:25:42.455029765 +0100 -@@ -1,6 +1,18 @@ - #ifndef ATTR_H - #define ATTR_H - -+/** -+ * The maximum line length for a gitattributes file. If the line exceeds this -+ * length we will ignore it. -+ */ -+#define ATTR_MAX_LINE_LENGTH 2048 -+ -+ /** -+ * The maximum size of the giattributes file. If the file exceeds this size we -+ * will ignore it. -+ */ -+#define ATTR_MAX_FILE_SIZE (100 * 1024 * 1024) -+ - /* An attribute is a pointer to this opaque structure */ - struct git_attr; - -diff -ur b/git-compat-util.h c/git-compat-util.h ---- b/git-compat-util.h 2023-02-21 11:27:58.038145942 +0100 -+++ c/git-compat-util.h 2023-02-21 12:27:18.836638388 +0100 -@@ -324,7 +324,9 @@ - extern NORETURN void die(const char *err, ...) __attribute__((format (printf, 1, 2))); - extern NORETURN void die_errno(const char *err, ...) __attribute__((format (printf, 1, 2))); - extern int error(const char *err, ...) __attribute__((format (printf, 1, 2))); -+extern int error_errno(const char *err, ...) __attribute__((format (printf, 1, 2))); - extern void warning(const char *err, ...) __attribute__((format (printf, 1, 2))); -+extern void warning_errno(const char *err, ...) __attribute__((format (printf, 1, 2))); - - /* - * Let callers be aware of the constant return value; this can help -@@ -524,8 +526,8 @@ - (uintmax_t)a, (uintmax_t)b); - return a + b; - } --#define st_add3(a,b,c) st_add((a),st_add((b),(c))) --#define st_add4(a,b,c,d) st_add((a),st_add3((b),(c),(d))) -+#define st_add3(a,b,c) st_add(st_add((a),(b)),(c)) -+#define st_add4(a,b,c,d) st_add(st_add3((a),(b),(c)),(d)) - - static inline size_t st_mult(size_t a, size_t b) - { -diff -ur b/t/t0003-attributes.sh c/t/t0003-attributes.sh ---- b/t/t0003-attributes.sh 2013-06-10 22:01:55.000000000 +0200 -+++ c/t/t0003-attributes.sh 2023-02-21 12:30:22.614804084 +0100 -@@ -245,39 +245,106 @@ - ' - - test_expect_success 'setup bare' ' -- git clone --bare . bare.git && -- cd bare.git -+ git clone --bare . bare.git - ' - - test_expect_success 'bare repository: check that .gitattribute is ignored' ' - ( -- echo "f test=f" -- echo "a/i test=a/i" -- ) >.gitattributes && -- attr_check f unspecified && -- attr_check a/f unspecified && -- attr_check a/c/f unspecified && -- attr_check a/i unspecified && -- attr_check subdir/a/i unspecified -+ cd bare.git && -+ ( -+ echo "f test=f" -+ echo "a/i test=a/i" -+ ) >.gitattributes && -+ attr_check f unspecified && -+ attr_check a/f unspecified && -+ attr_check a/c/f unspecified && -+ attr_check a/i unspecified && -+ attr_check subdir/a/i unspecified -+ ) - ' - - test_expect_success 'bare repository: check that --cached honors index' ' -- GIT_INDEX_FILE=../.git/index \ -- git check-attr --cached --stdin --all <../stdin-all | -- sort >actual && -- test_cmp ../specified-all actual -+ ( -+ cd bare.git && -+ GIT_INDEX_FILE=../.git/index \ -+ git check-attr --cached --stdin --all <../stdin-all | -+ sort >actual && -+ test_cmp ../specified-all actual -+ ) - ' - - test_expect_success 'bare repository: test info/attributes' ' - ( -- echo "f test=f" -- echo "a/i test=a/i" -- ) >info/attributes && -- attr_check f f && -- attr_check a/f f && -- attr_check a/c/f f && -- attr_check a/i a/i && -- attr_check subdir/a/i unspecified -+ cd bare.git && -+ ( -+ echo "f test=f" -+ echo "a/i test=a/i" -+ ) >info/attributes && -+ attr_check f f && -+ attr_check a/f f && -+ attr_check a/c/f f && -+ attr_check a/i a/i && -+ attr_check subdir/a/i unspecified -+ ) -+' -+ -+test_expect_success 'large attributes line ignored in tree' ' -+ test_when_finished "rm .gitattributes" && -+ printf "path %02043d" 1 >.gitattributes && -+ git check-attr --all path >actual 2>err && -+ echo "warning: ignoring overly long attributes line 1" >expect && -+ test_cmp expect err && -+ test_must_be_empty actual -+' -+ -+test_expect_success 'large attributes line ignores trailing content in tree' ' -+ test_when_finished "rm .gitattributes" && -+ # older versions of Git broke lines at 2048 bytes; the 2045 bytes -+ # of 0-padding here is accounting for the three bytes of "a 1", which -+ # would knock "trailing" to the "next" line, where it would be -+ # erroneously parsed. -+ printf "a %02045dtrailing attribute\n" 1 >.gitattributes && -+ git check-attr --all trailing >actual 2>err && -+ echo "warning: ignoring overly long attributes line 1" >expect && -+ test_cmp expect err && -+ test_must_be_empty actual -+' -+ -+test_expect_success EXPENSIVE 'large attributes file ignored in tree' ' -+ test_when_finished "rm .gitattributes" && -+ dd if=/dev/zero of=.gitattributes bs=101M count=1 2>/dev/null && -+ git check-attr --all path >/dev/null 2>err && -+ echo "warning: ignoring overly large gitattributes file ${SQ}.gitattributes${SQ}" >expect && -+ test_cmp expect err -+' -+ -+test_expect_success 'large attributes line ignored in index' ' -+ test_when_finished "git update-index --remove .gitattributes" && -+ blob=$(printf "path %02043d" 1 | git hash-object -w --stdin) && -+ git update-index --add --cacheinfo 100644 $blob .gitattributes && -+ git check-attr --cached --all path >actual 2>err && -+ echo "warning: ignoring overly long attributes line 1" >expect && -+ test_cmp expect err && -+ test_must_be_empty actual -+' -+ -+test_expect_success 'large attributes line ignores trailing content in index' ' -+ test_when_finished "git update-index --remove .gitattributes" && -+ blob=$(printf "a %02045dtrailing attribute\n" 1 | git hash-object -w --stdin) && -+ git update-index --add --cacheinfo 100644 $blob .gitattributes && -+ git check-attr --cached --all trailing >actual 2>err && -+ echo "warning: ignoring overly long attributes line 1" >expect && -+ test_cmp expect err && -+ test_must_be_empty actual -+' -+ -+test_expect_success EXPENSIVE 'large attributes file ignored in index' ' -+ test_when_finished "git update-index --remove .gitattributes" && -+ blob=$(dd if=/dev/zero bs=101M count=1 2>/dev/null | git hash-object -w --stdin) && -+ git update-index --add --cacheinfo 100644 $blob .gitattributes && -+ git check-attr --cached --all path >/dev/null 2>err && -+ echo "warning: ignoring overly large gitattributes blob ${SQ}.gitattributes${SQ}" >expect && -+ test_cmp expect err - ' - - test_done -diff -ur b/t/test-lib-functions.sh c/t/test-lib-functions.sh ---- b/t/test-lib-functions.sh 2013-06-10 22:01:55.000000000 +0200 -+++ c/t/test-lib-functions.sh 2023-02-21 12:31:24.357204323 +0100 -@@ -609,6 +609,20 @@ - $GIT_TEST_CMP "$@" - } - -+# Check if the file expected to be empty is indeed empty, and barfs -+# otherwise. -+ -+test_must_be_empty () { -+ test "$#" -ne 1 && BUG "1 param" -+ test_path_is_file "$1" && -+ if test -s "$1" -+ then -+ echo "'$1' is not empty, it contains:" -+ cat "$1" -+ return 1 -+ fi -+} -+ - # Tests that its two parameters refer to the same revision - test_cmp_rev () { - git rev-parse --verify "$1" >expect.rev && -diff -ur b/t/test-lib.sh c/t/test-lib.sh ---- b/t/test-lib.sh 2023-02-21 11:52:24.739202530 +0100 -+++ c/t/test-lib.sh 2023-02-21 12:31:52.866389106 +0100 -@@ -153,6 +153,9 @@ - LF=' - ' - -+# Single quote -+SQ=\' -+ - export _x05 _x40 _z40 LF - - # Each test should start with something like this, after copyright notices: -diff -ur b/usage.c c/usage.c ---- b/usage.c 2013-06-10 22:01:55.000000000 +0200 -+++ c/usage.c 2023-02-21 12:32:56.807803579 +0100 -@@ -104,6 +104,30 @@ - va_end(params); - } - -+static const char *fmt_with_err(char *buf, int n, const char *fmt) -+{ -+ char str_error[256], *err; -+ int i, j; -+ -+ err = strerror(errno); -+ for (i = j = 0; err[i] && j < sizeof(str_error) - 1; ) { -+ if ((str_error[j++] = err[i++]) != '%') -+ continue; -+ if (j < sizeof(str_error) - 1) { -+ str_error[j++] = '%'; -+ } else { -+ /* No room to double the '%', so we overwrite it with -+ * '\0' below */ -+ j--; -+ break; -+ } -+ } -+ str_error[j] = 0; -+ /* Truncation is acceptable here */ -+ snprintf(buf, n, "%s: %s", fmt, str_error); -+ return buf; -+} -+ - void NORETURN die_errno(const char *fmt, ...) - { - va_list params; -@@ -149,6 +173,16 @@ - return -1; - } - -+void warning_errno(const char *warn, ...) -+{ -+ char buf[1024]; -+ va_list params; -+ -+ va_start(params, warn); -+ warn_routine(fmt_with_err(buf, sizeof(buf), warn), params); -+ va_end(params); -+} -+ - void warning(const char *warn, ...) - { - va_list params; -diff -ur b/utf8.c c/utf8.c ---- b/utf8.c 2023-02-21 12:00:28.555925285 +0100 -+++ c/utf8.c 2023-02-21 12:33:48.863141018 +0100 -@@ -639,3 +639,14 @@ - - return chrlen; - } -+ -+const char utf8_bom[] = "\357\273\277"; -+ -+int skip_utf8_bom(char **text, size_t len) -+{ -+ if (len < strlen(utf8_bom) || -+ memcmp(*text, utf8_bom, strlen(utf8_bom))) -+ return 0; -+ *text += strlen(utf8_bom); -+ return 1; -+} -diff -ur b/utf8.h c/utf8.h ---- b/utf8.h 2023-02-21 12:00:40.186991497 +0100 -+++ c/utf8.h 2023-02-21 12:34:19.536339834 +0100 -@@ -12,6 +12,9 @@ - int same_encoding(const char *, const char *); - int utf8_fprintf(FILE *, const char *, ...); - -+extern const char utf8_bom[]; -+int skip_utf8_bom(char **, size_t); -+ - void strbuf_add_wrapped_text(struct strbuf *buf, - const char *text, int indent, int indent2, int width); - void strbuf_add_wrapped_bytes(struct strbuf *buf, const char *data, int len, diff --git a/git-cve-2022-41903.patch b/git-cve-2022-41903.patch deleted file mode 100644 index de8cc01c140a642703464ddd6756cbe28c359860..0000000000000000000000000000000000000000 --- a/git-cve-2022-41903.patch +++ /dev/null @@ -1,401 +0,0 @@ -diff -ur a/git-compat-util.h b/git-compat-util.h ---- a/git-compat-util.h 2023-02-01 16:15:48.875070563 +0100 -+++ b/git-compat-util.h 2023-02-21 11:27:58.038145942 +0100 -@@ -543,6 +543,13 @@ - return a - b; - } - -+static inline int cast_size_t_to_int(size_t a) -+{ -+ if (a > INT_MAX) -+ die("number too large to represent as int on this platform: %"PRIuMAX, -+ (uintmax_t)a); -+ return (int)a; -+} - - extern char *xstrdup(const char *str); - extern void *xmalloc(size_t size); -diff -ur a/pretty.c b/pretty.c ---- a/pretty.c 2013-06-10 22:01:55.000000000 +0200 -+++ b/pretty.c 2023-02-21 11:18:11.584775725 +0100 -@@ -11,6 +11,13 @@ - #include "reflog-walk.h" - #include "gpg-interface.h" - -+/* -+ * The limit for formatting directives, which enable the caller to append -+ * arbitrarily many bytes to the formatted buffer. This includes padding -+ * and wrapping formatters. -+ */ -+#define FORMATTING_LIMIT (16 * 1024) -+ - static char *user_format; - static struct cmt_fmt_map { - const char *name; -@@ -917,7 +924,9 @@ - if (pos) - strbuf_add(&tmp, sb->buf, pos); - strbuf_add_wrapped_text(&tmp, sb->buf + pos, -- (int) indent1, (int) indent2, (int) width); -+ cast_size_t_to_int(indent1), -+ cast_size_t_to_int(indent2), -+ cast_size_t_to_int(width)); - strbuf_swap(&tmp, sb); - strbuf_release(&tmp); - } -@@ -1030,9 +1039,18 @@ - const char *end = start + strcspn(start, ",)"); - char *next; - int width; -- if (!end || end == start) -+ if (!*end || end == start) - return 0; - width = strtoul(start, &next, 10); -+ -+ /* -+ * We need to limit the amount of padding, or otherwise this -+ * would allow the user to pad the buffer by arbitrarily many -+ * bytes and thus cause resource exhaustion. -+ */ -+ if (width < -FORMATTING_LIMIT || width > FORMATTING_LIMIT) -+ return 0; -+ - if (next == start || width == 0) - return 0; - c->padding = to_column ? -width : width; -@@ -1119,6 +1137,16 @@ - if (*next != ')') - return 0; - } -+ -+ /* -+ * We need to limit the format here as it allows the -+ * user to prepend arbitrarily many bytes to the buffer -+ * when rewrapping. -+ */ -+ if (width > FORMATTING_LIMIT || -+ indent1 > FORMATTING_LIMIT || -+ indent2 > FORMATTING_LIMIT) -+ return 0; - rewrap_message_tail(sb, c, width, indent1, indent2); - return end - placeholder + 1; - } else -@@ -1296,18 +1324,20 @@ - struct format_commit_context *c) - { - struct strbuf local_sb = STRBUF_INIT; -- int total_consumed = 0, len, padding = c->padding; -+ size_t total_consumed = 0; -+ int len, padding = c->padding; -+ - if (padding < 0) { - const char *start = strrchr(sb->buf, '\n'); - int occupied; - if (!start) - start = sb->buf; -- occupied = utf8_strnwidth(start, -1, 1); -+ occupied = utf8_strnwidth(start, strlen(start), 1); - padding = (-padding) - occupied; - } - while (1) { - int modifier = *placeholder == 'C'; -- int consumed = format_commit_one(&local_sb, placeholder, c); -+ size_t consumed = format_commit_one(&local_sb, placeholder, c); - total_consumed += consumed; - - if (!modifier) -@@ -1319,7 +1349,7 @@ - placeholder++; - total_consumed++; - } -- len = utf8_strnwidth(local_sb.buf, -1, 1); -+ len = utf8_strnwidth(local_sb.buf, local_sb.len, 1); - - if (c->flush_type == flush_left_and_steal) { - const char *ch = sb->buf + sb->len - 1; -@@ -1334,7 +1364,7 @@ - if (*ch != 'm') - break; - p = ch - 1; -- while (ch - p < 10 && *p != '\033') -+ while (p > sb->buf && ch - p < 10 && *p != '\033') - p--; - if (*p != '\033' || - ch + 1 - p != display_mode_esc_sequence_len(p)) -@@ -1373,7 +1403,7 @@ - } - strbuf_addstr(sb, local_sb.buf); - } else { -- int sb_len = sb->len, offset = 0; -+ size_t sb_len = sb->len, offset = 0; - if (c->flush_type == flush_left) - offset = padding - len; - else if (c->flush_type == flush_both) -@@ -1398,8 +1428,7 @@ - const char *placeholder, - void *context) - { -- int consumed; -- size_t orig_len; -+ size_t consumed, orig_len; - enum { - NO_MAGIC, - ADD_LF_BEFORE_NON_EMPTY, -@@ -1420,9 +1449,21 @@ - default: - break; - } -- if (magic != NO_MAGIC) -+ if (magic != NO_MAGIC) { - placeholder++; - -+ switch (placeholder[0]) { -+ case 'w': -+ /* -+ * `%+w()` cannot ever expand to a non-empty string, -+ * and it potentially changes the layout of preceding -+ * contents. We're thus not able to handle the magic in -+ * this combination and refuse the pattern. -+ */ -+ return 0; -+ }; -+ } -+ - orig_len = sb->len; - if (((struct format_commit_context *)context)->flush_type != no_flush) - consumed = format_and_pad_commit(sb, placeholder, context); -diff -ur a/t/t4205-log-pretty-formats.sh b/t/t4205-log-pretty-formats.sh ---- a/t/t4205-log-pretty-formats.sh 2013-06-10 22:01:55.000000000 +0200 -+++ b/t/t4205-log-pretty-formats.sh 2023-02-21 11:19:31.345309067 +0100 -@@ -274,4 +274,80 @@ - test_cmp expected actual - ' - -+test_expect_success 'log --pretty with space stealing' ' -+ printf mm0 >expect && -+ git log -1 --pretty="format:mm%>>|(1)%x30" >actual && -+ test_cmp expect actual -+' -+ -+test_expect_success 'log --pretty with invalid padding format' ' -+ printf "%s%%<(20" "$(git rev-parse HEAD)" >expect && -+ git log -1 --pretty="format:%H%<(20" >actual && -+ test_cmp expect actual -+' -+ -+test_expect_success 'log --pretty with magical wrapping directives' ' -+ commit_id=$(git commit-tree HEAD^{tree} -m "describe me") && -+ git tag describe-me $commit_id && -+ printf "\n(tag:\ndescribe-me)%%+w(2)" >expect && -+ git log -1 --pretty="format:%w(1)%+d%+w(2)" $commit_id >actual && -+ test_cmp expect actual -+' -+ -+test_expect_success SIZE_T_IS_64BIT 'log --pretty with overflowing wrapping directive' ' -+ printf "%%w(2147483649,1,1)0" >expect && -+ git log -1 --pretty="format:%w(2147483649,1,1)%x30" >actual && -+ test_cmp expect actual && -+ printf "%%w(1,2147483649,1)0" >expect && -+ git log -1 --pretty="format:%w(1,2147483649,1)%x30" >actual && -+ test_cmp expect actual && -+ printf "%%w(1,1,2147483649)0" >expect && -+ git log -1 --pretty="format:%w(1,1,2147483649)%x30" >actual && -+ test_cmp expect actual -+' -+ -+test_expect_success SIZE_T_IS_64BIT 'log --pretty with overflowing padding directive' ' -+ printf "%%<(2147483649)0" >expect && -+ git log -1 --pretty="format:%<(2147483649)%x30" >actual && -+ test_cmp expect actual -+' -+ -+test_expect_success 'log --pretty with padding and preceding control chars' ' -+ printf "\20\20 0" >expect && -+ git log -1 --pretty="format:%x10%x10%>|(4)%x30" >actual && -+ test_cmp expect actual -+' -+ -+test_expect_success 'log --pretty truncation with control chars' ' -+ test_commit "$(printf "\20\20\20\20xxxx")" file contents commit-with-control-chars && -+ printf "\20\20\20\20x.." >expect && -+ git log -1 --pretty="format:%<(3,trunc)%s" commit-with-control-chars >actual && -+ test_cmp expect actual -+' -+ -+test_expect_success EXPENSIVE,SIZE_T_IS_64BIT 'log --pretty with huge commit message' ' -+ # We only assert that this command does not crash. This needs to be -+ # executed with the address sanitizer to demonstrate failure. -+ git log -1 --pretty="format:%>(2147483646)%x41%41%>(2147483646)%x41" >/dev/null -+' -+ -+test_expect_success EXPENSIVE,SIZE_T_IS_64BIT 'set up huge commit' ' -+ test-tool genzeros 2147483649 | tr "\000" "1" >expect && -+ huge_commit=$(git commit-tree -F expect HEAD^{tree}) -+' -+ -+test_expect_success EXPENSIVE,SIZE_T_IS_64BIT 'log --pretty with huge commit message' ' -+ git log -1 --format="%B%<(1)%x30" $huge_commit >actual && -+ echo 0 >>expect && -+ test_cmp expect actual -+' -+ -+test_expect_success EXPENSIVE,SIZE_T_IS_64BIT 'log --pretty with huge commit message does not cause allocation failure' ' -+ test_must_fail git log -1 --format="%<(1)%B" $huge_commit 2>error && -+ cat >expect <<-EOF && -+ fatal: number too large to represent as int on this platform: 2147483649 -+ EOF -+ test_cmp expect error -+' -+ - test_done -diff -ur a/t/test-lib.sh b/t/test-lib.sh ---- a/t/test-lib.sh 2013-06-10 22:01:55.000000000 +0200 -+++ b/t/test-lib.sh 2023-02-21 11:52:24.739202530 +0100 -@@ -770,6 +770,10 @@ - git var GIT_AUTHOR_IDENT - ' - -+test_lazy_prereq SIZE_T_IS_64BIT ' -+ test 8 -eq "$(build_option sizeof-size_t)" -+' -+ - # When the tests are run as root, permission tests will report that - # things are writable when they shouldn't be. - test -w / || test_set_prereq SANITY -diff -ur a/utf8.c b/utf8.c ---- a/utf8.c 2013-06-10 22:01:55.000000000 +0200 -+++ b/utf8.c 2023-02-21 12:00:28.555925285 +0100 -@@ -266,26 +266,32 @@ - * string, assuming that the string is utf8. Returns strlen() instead - * if the string does not look like a valid utf8 string. - */ --int utf8_strnwidth(const char *string, int len, int skip_ansi) -+int utf8_strnwidth(const char *string, size_t len, int skip_ansi) - { -- int width = 0; - const char *orig = string; -+ size_t width = 0; - -- if (len == -1) -- len = strlen(string); - while (string && string < orig + len) { -- int skip; -+ int glyph_width; -+ size_t skip; - while (skip_ansi && - (skip = display_mode_esc_sequence_len(string)) != 0) - string += skip; -- width += utf8_width(&string, NULL); -+ glyph_width = utf8_width(&string, NULL); -+ if (glyph_width > 0) -+ width += glyph_width; - } -- return string ? width : len; -+ -+ /* -+ * TODO: fix the interface of this function and `utf8_strwidth()` to -+ * return `size_t` instead of `int`. -+ */ -+ return cast_size_t_to_int(string ? width : len); - } - - int utf8_strwidth(const char *string) - { -- return utf8_strnwidth(string, -1, 0); -+ return utf8_strnwidth(string, strlen(string), 0); - } - - int is_utf8(const char *text) -@@ -424,47 +430,52 @@ - void strbuf_utf8_replace(struct strbuf *sb_src, int pos, int width, - const char *subst) - { -- struct strbuf sb_dst = STRBUF_INIT; -- char *src = sb_src->buf; -- char *end = src + sb_src->len; -- char *dst; -- int w = 0, subst_len = 0; -- -- if (subst) -- subst_len = strlen(subst); -- strbuf_grow(&sb_dst, sb_src->len + subst_len); -- dst = sb_dst.buf; -+ const char *src = sb_src->buf, *end = sb_src->buf + sb_src->len; -+ struct strbuf dst; -+ int w = 0; -+ -+ strbuf_init(&dst, sb_src->len); - - while (src < end) { -- char *old; -+ const char *old; -+ int glyph_width; - size_t n; - - while ((n = display_mode_esc_sequence_len(src))) { -- memcpy(dst, src, n); -+ strbuf_add(&dst, src, n); - src += n; -- dst += n; - } -+ -+ if (src >= end) -+ break; - - old = src; -- n = utf8_width((const char**)&src, NULL); -- if (!src) /* broken utf-8, do nothing */ -- return; -- if (n && w >= pos && w < pos + width) { -+ glyph_width = utf8_width((const char**)&src, NULL); -+ if (!src) /* broken utf-8, do nothing */ -+ goto out; -+ -+ /* -+ * In case we see a control character we copy it into the -+ * buffer, but don't add it to the width. -+ */ -+ if (glyph_width < 0) -+ glyph_width = 0; -+ -+ if (glyph_width && w >= pos && w < pos + width) { - if (subst) { -- memcpy(dst, subst, subst_len); -- dst += subst_len; -+ strbuf_addstr(&dst, subst); - subst = NULL; - } -- w += n; -- continue; -+ } else { -+ strbuf_add(&dst, old, src - old); - } -- memcpy(dst, old, src - old); -- dst += src - old; -- w += n; -- } -- strbuf_setlen(&sb_dst, dst - sb_dst.buf); -- strbuf_swap(sb_src, &sb_dst); -- strbuf_release(&sb_dst); -+ -+ w += glyph_width; -+ } -+ -+ strbuf_swap(sb_src, &dst); -+out: -+ strbuf_release(&dst); - } - - int is_encoding_utf8(const char *name) -diff -ur a/utf8.h b/utf8.h ---- a/utf8.h 2013-06-10 22:01:55.000000000 +0200 -+++ b/utf8.h 2023-02-21 12:00:40.186991497 +0100 -@@ -5,7 +5,7 @@ - - size_t display_mode_esc_sequence_len(const char *s); - int utf8_width(const char **start, size_t *remainder_p); --int utf8_strnwidth(const char *string, int len, int skip_ansi); -+int utf8_strnwidth(const char *string, size_t len, int skip_ansi); - int utf8_strwidth(const char *string); - int is_utf8(const char *text); - int is_encoding_utf8(const char *name); diff --git a/git-cve-2023-25652.patch b/git-cve-2023-25652.patch deleted file mode 100644 index bf42f53049f619c12c7e0639ef05bc424fcccc50..0000000000000000000000000000000000000000 --- a/git-cve-2023-25652.patch +++ /dev/null @@ -1,54 +0,0 @@ -diff -up ./builtin/apply.c.original ./builtin/apply.c ---- ./builtin/apply.c.original 2023-05-18 14:44:01.287997256 +0900 -+++ ./builtin/apply.c 2023-05-18 14:44:45.513756974 +0900 -@@ -4151,7 +4151,7 @@ static int write_out_one_reject(struct p - FILE *rej; - char namebuf[PATH_MAX]; - struct fragment *frag; -- int cnt = 0; -+ int fd, cnt = 0; - struct strbuf sb = STRBUF_INIT; - - for (cnt = 0, frag = patch->fragments; frag; frag = frag->next) { -@@ -4190,7 +4190,17 @@ static int write_out_one_reject(struct p - memcpy(namebuf, patch->new_name, cnt); - memcpy(namebuf + cnt, ".rej", 5); - -- rej = fopen(namebuf, "w"); -+ fd = open(namebuf, O_CREAT | O_EXCL | O_WRONLY, 0666); -+ if (fd < 0) { -+ if (errno != EEXIST) -+ die_errno(_("cannot open %s"), namebuf); -+ if (unlink(namebuf)) -+ die_errno(_("cannot unlink '%s'"), namebuf); -+ fd = open(namebuf, O_CREAT | O_EXCL | O_WRONLY, 0666); -+ if (fd < 0) -+ die_errno(_("cannot open %s"), namebuf); -+ } -+ rej = fdopen(fd, "w"); - if (!rej) - return error(_("cannot open %s: %s"), namebuf, strerror(errno)); - -diff -up ./t/t4115-apply-symlink.sh.original ./t/t4115-apply-symlink.sh ---- ./t/t4115-apply-symlink.sh.original 2023-05-18 14:45:50.675876356 +0900 -+++ ./t/t4115-apply-symlink.sh 2023-05-18 14:46:42.075759315 +0900 -@@ -46,4 +46,19 @@ test_expect_success SYMLINKS 'apply --in - - ' - -+test_expect_success SYMLINKS '--reject removes .rej symlink if it exists' ' -+ test_when_finished "git reset --hard && git clean -dfx" && -+ -+ test_commit file && -+ echo modified >file.t && -+ git diff -- file.t >patch && -+ echo modified-again >file.t && -+ -+ ln -s foo file.t.rej && -+ test_must_fail git apply patch --reject 2>err && -+ test_i18ngrep "Rejected hunk" err && -+ test_path_is_missing foo && -+ test_path_is_file file.t.rej -+' -+ - test_done diff --git a/git-cve-2023-29007.patch b/git-cve-2023-29007.patch deleted file mode 100644 index 0e4792c5633d7b365f03b9de344650e97cdce730..0000000000000000000000000000000000000000 --- a/git-cve-2023-29007.patch +++ /dev/null @@ -1,100 +0,0 @@ -diff -up ./config.c.original ./config.c ---- ./config.c.original 2023-05-18 15:11:28.221121569 +0900 -+++ ./config.c 2023-05-18 15:24:30.178828343 +0900 -@@ -1701,6 +1701,8 @@ static int section_name_is_ok(const char - return 1; - } - -+#define GIT_CONFIG_MAX_LINE_LEN (512 * 1024) -+ - /* if new_name == NULL, the section is removed instead */ - int git_config_rename_section_in_file(const char *config_filename, - const char *old_name, const char *new_name) -@@ -1709,8 +1711,9 @@ int git_config_rename_section_in_file(co - char *filename_buf = NULL; - struct lock_file *lock; - int out_fd; -- char buf[1024]; -+ struct strbuf buf = STRBUF_INIT; - FILE *config_file; -+ uint32_t line_nr = 0; - - if (new_name && !section_name_is_ok(new_name)) { - ret = error("invalid section name: %s", new_name); -@@ -1732,15 +1735,25 @@ int git_config_rename_section_in_file(co - goto unlock_and_out; - } - -- while (fgets(buf, sizeof(buf), config_file)) { -+ while (!strbuf_getwholeline(&buf, config_file, '\n')) { - int i; - int length; -- char *output = buf; -- for (i = 0; buf[i] && isspace(buf[i]); i++) -+ char *output = buf.buf; -+ -+ line_nr++; -+ -+ if (buf.len >= GIT_CONFIG_MAX_LINE_LEN) { -+ ret = error(_("refusing to work with overly long line " -+ "in '%s' on line %"PRIuMAX), -+ config_filename, (uintmax_t)line_nr); -+ goto out; -+ } -+ -+ for (i = 0; buf.buf[i] && isspace(buf.buf[i]); i++) - ; /* do nothing */ -- if (buf[i] == '[') { -+ if (buf.buf[i] == '[') { - /* it's a section */ -- int offset = section_name_match(&buf[i], old_name); -+ int offset = section_name_match(&buf.buf[i], old_name); - if (offset > 0) { - ret++; - if (new_name == NULL) { -@@ -1785,6 +1798,7 @@ unlock_and_out: - ret = error("could not commit config file %s", config_filename); - out: - free(filename_buf); -+ strbuf_release(&buf); - return ret; - } - -diff -up ./t/t1300-repo-config.sh.original ./t/t1300-repo-config.sh ---- ./t/t1300-repo-config.sh.original 2023-05-18 15:17:53.636877440 +0900 -+++ ./t/t1300-repo-config.sh 2023-05-18 15:25:16.931647850 +0900 -@@ -1122,4 +1122,34 @@ test_expect_failure 'adding a key into a - test_cmp expect .git/config - ' - -+test_expect_success 'renaming a section with a long line' ' -+ { -+ printf "[b]\\n" && -+ printf " c = d %1024s [a] e = f\\n" " " && -+ printf "[a] g = h\\n" -+ } >y && -+ git config -f y --rename-section a xyz && -+ test_must_fail git config -f y b.e -+' -+ -+test_expect_success 'renaming an embedded section with a long line' ' -+ { -+ printf "[b]\\n" && -+ printf " c = d %1024s [a] [foo] e = f\\n" " " && -+ printf "[a] g = h\\n" -+ } >y && -+ git config -f y --rename-section a xyz && -+ test_must_fail git config -f y foo.e -+' -+ -+test_expect_success 'renaming a section with an overly-long line' ' -+ { -+ printf "[b]\\n" && -+ printf " c = d %525000s e" " " && -+ printf "[a] g = h\\n" -+ } >y && -+ test_must_fail git config -f y --rename-section a xyz 2>err && -+ test_i18ngrep "refusing to work with overly long line in .y. on line 2" err -+' -+ - test_done diff --git a/git-cvsimport-Ignore-cvsps-2.2b1-Branches-output.patch b/git-cvsimport-Ignore-cvsps-2.2b1-Branches-output.patch deleted file mode 100644 index 37a22dd578057e414265f7b8100f225b0d8291da..0000000000000000000000000000000000000000 --- a/git-cvsimport-Ignore-cvsps-2.2b1-Branches-output.patch +++ /dev/null @@ -1,26 +0,0 @@ -From 09891c65a5f7409ce0bd37daced0ff31fbb1b1c9 Mon Sep 17 00:00:00 2001 -From: Todd Zullinger -Date: Mon, 23 Mar 2009 00:03:36 -0400 -Subject: [PATCH] git-cvsimport: Ignore cvsps-2.2b1 Branches: output - -Signed-off-by: Todd Zullinger ---- - git-cvsimport.perl | 2 +- - 1 files changed, 1 insertions(+), 1 deletions(-) - -diff --git a/git-cvsimport.perl b/git-cvsimport.perl -index e439202..d020f1a 100755 ---- a/git-cvsimport.perl -+++ b/git-cvsimport.perl -@@ -952,7 +952,7 @@ while () { - } elsif (/^-+$/) { # end of unknown-line processing - $state = 1; - } elsif ($state != 11) { # ignore stuff when skipping -- print STDERR "* UNKNOWN LINE * $_\n"; -+ print STDERR "* UNKNOWN LINE * $_\n" unless /^Branches: /; - } - } - commit() if $branch and $state != 11; --- -1.6.2.2 - diff --git a/git-request-pull-fix.patch b/git-request-pull-fix.patch deleted file mode 100644 index 264985d021b4d1d1d756fbb9e4cae2883409fad9..0000000000000000000000000000000000000000 --- a/git-request-pull-fix.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/git-request-pull.sh b/git-request-pull.sh -index d566015..71abbf4 100755 ---- a/git-request-pull.sh -+++ b/git-request-pull.sh -@@ -79,7 +79,7 @@ find_matching_ref=' - my ($sha1, $ref, $deref) = /^(\S+)\s+(\S+?)(\^\{\})?$/; - next unless ($sha1 eq $ARGV[1]); - $found = abbr($ref); -- if ($deref && $ref eq "tags/$ARGV[2]") { -+ if ($deref && $ref eq "refs/tags/$ARGV[2]") { - $tagged = $found; - last; - } diff --git a/git.spec b/git.spec index 7840c296459f0d7eb04d0d50ed584f80f6c25cf2..fdefe966faa433c191c33a15c2526be1187e1895 100644 --- a/git.spec +++ b/git.spec @@ -1,717 +1,325 @@ -# Pass --without docs to rpmbuild if you don't want the documentation -%bcond_without docs - -%global gitcoredir %{_libexecdir}/git-core - -# Use systemd instead of xinetd on Fedora 19+ and RHEL 7+ -%if 0%{?fedora} >= 19 || 0%{?rhel} >= 7 -%global use_systemd 1 -%else -%global use_systemd 0 -%endif - -# Build gnome-keyring git-credential helper on Fedora and RHEL >= 7 -%if 0%{?fedora} || 0%{?rhel} >= 7 -%global gnome_keyring 1 -%else -%global gnome_keyring 0 -%endif - -%if (0%{?fedora} && 0%{?fedora} < 19) || (0%{?rhel} && 0%{?rhel} < 7) -%global with_desktop_vendor_tag 1 -%else -%global with_desktop_vendor_tag 0 -%endif - -# https://fedoraproject.org/wiki/Changes/PythonMacroError -%define __python /usr/bin/python - - -Name: git -Version: 1.8.3.1 -Release: 25%{?dist} -Summary: Fast Version Control System -License: GPLv2 -Group: Development/Tools -URL: http://git-scm.com/ -Source0: http://git-core.googlecode.com/files/%{name}-%{version}.tar.gz -Source2: git-init.el -Source3: git.xinetd.in -Source4: git.conf.httpd -Source5: git-gui.desktop -Source6: gitweb.conf.in -Source12: git@.service -Source13: git.socket -Patch0: git-1.5-gitweb-home-link.patch -# https://bugzilla.redhat.com/490602 -Patch1: git-cvsimport-Ignore-cvsps-2.2b1-Branches-output.patch -Patch5: 0001-git-subtree-Use-gitexecdir-instead-of-libexecdir.patch -# This fixes the build when python is enabled. Needs discussion upstream to -# find a proper solution. -Patch6: 0001-Drop-DESTDIR-from-python-instlibdir.patch - -# whole set is for https://bugzilla.redhat.com/show_bug.cgi?id=1273889 -Patch7: 0001-submodule-allow-only-certain-protocols-for-submodule.patch -Patch8: 0002-transport-add-a-protocol-whitelist-environment-varia.patch -Patch9: 0003-transport-refactor-protocol-whitelist-code.patch -Patch10: 0004-http-limit-redirection-to-protocol-whitelist.patch -Patch11: 0005-http-limit-redirection-depth.patch - -# various non-CVE bugs -Patch13: 0001-http-control-GSSAPI-credential-delegation.patch -Patch17: 0009-remote-curl-fall-back-to-Basic-auth-if-Negotiate-fai.patch -Patch18: git-request-pull-fix.patch - -# CVE -Patch12: 0001-Fix-CVE-2016-2315-CVE-2016-2324.patch -Patch14: 0007-git-prompt.patch -Patch15: 0008-Fix-CVE-2017-8386.patch -Patch16: git-cve-2017-1000117.patch -Patch19: git-cve-2018-11235.patch -Patch20: 0001-Switch-git-instaweb-default-to-apache.patch -Patch21: git-cve-2018-17456.patch -Patch22: git-cve-2018-17456-tests.patch -# https://repo.or.cz/git/debian.git/commit/392f99a5d2174e6124f829d034bac6755c33119d -Patch23: git-cve-2019-1387.patch -# https://bugzilla.redhat.com/show_bug.cgi?id=1822020 -Patch24: git-cve-2020-5260.patch -# Fix CVE-2020-11008: Crafted URL containing new lines, empty host or lacks -# a scheme can cause credential leak. -# https://bugzilla.redhat.com/show_bug.cgi?id=1826001 -# The fix includes all patches from: -# https://github.com/git/git/compare/v2.17.4...v2.17.5 -# except a88dbd2 and df5be6d. The patches differs from their upstream versions -# several things: -# 1) `BUG()` macro is not supported yet and it is replaced by call to -# `die("BUG: ...` which used to be common pattern. -# 2) the `skip_prefix` has older signature and does not return boolean yet. -# 3) `git -C dst` is not supported yet and it is replaced by `cd dst`. -# 4) `grep gitmodulesUrl` is replaced by grep for a related string, because -# these IDs were introduced in later versions. -# 5) t/t5550-http-fetch.sh was renamed to t/t5550-http-fetch-dumb.sh in later -# versions. -# On top of that, there are included commit 9566231, c716fe4 and 07259e7, -# which provides necessary plumbing. -Patch25: git-2.17.15-CVE-2020-11008.patch -# Fix CVE-2022-41903: Heap overflow in `git archive`, `git log --format` -# leading to RCE -# https://bugzilla.redhat.com/show_bug.cgi?id=2162056 -# The fix includes all pretty and utf8 patches from Dec 9,2022: -# https://github.com/git/git/compare/v2.30.6...v2.30.7 -Patch26: git-cve-2022-41903.patch -# Fix CVE-2022-23521: gitattributes parsing integer overflow -# https://bugzilla.redhat.com/show_bug.cgi?id=2162055 -# The fix includes all attr patches from Dec 5,2022: -# https://github.com/git/git/compare/v2.30.6...v2.30.7 -# Some of these patches heavily differ from upstream as there has been -# a lot of changes to attribute handling in past years. For example there -# is no internal hashmap implementation, so the patches are adapted to the old one. -# It also includes: -# For plumbing: -# 428103c convert 'invalid_attr_name()' to 'attr_name_valid()' -# dde843e partially backported for skip_utf8_bom() -# fd1d672 + 58e4e51 partially backported for warning_errno() -# d616fbf to catch compiler error -# For tests: -# ca8d148 + 9eb2308 + e7884b3 all partially backported for test_must_be_empty() -# bd482d6 partially backported to support $SQ variable -# c4a7bce to not crash new tests in t0003 -Patch27: git-cve-2022-23521.patch -# Fix CVE-2023-25652: git: by feeding specially crafted input to `git apply --reject`, -# a path outside the working tree can be overwritten with partially controlled contents -# https://bugzilla.redhat.com/show_bug.cgi?id=2188333 -# The fix includes apply --reject patch from Apr 18,2023 -# https://github.com/git/git/compare/v2.30.8...v2.30.9 -# The relevant commit is: -# 9db0571 apply --reject: overwrite existing .rej symlink if it exists -Patch28: git-cve-2023-25652.patch -# Fix CVE-2023-29007: git: arbitrary configuration injection when renaming or -# deleting a section from a configuration file -# https://bugzilla.redhat.com/show_bug.cgi?id=2188338 -# The fix includes some of section rename patch from Apr 18,2023 -# https://github.com/git/git/compare/v2.30.8...v2.30.9 -# The relevent commits are -# a5bb10f config: avoid fixed-sized buffer when renaming/deleting a section -# 3bb3d6b config.c: disallow overly-long lines in `copy_or_rename_section_in_file()` -# 2919821 t1300: demonstrate failure when renaming sections with long lines -Patch29: git-cve-2023-29007.patch - -BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) - -%if %{with docs} -BuildRequires: asciidoc >= 8.4.1 -BuildRequires: xmlto -%endif -BuildRequires: desktop-file-utils -BuildRequires: emacs -BuildRequires: expat-devel -BuildRequires: gettext -BuildRequires: libcurl-devel -%if %{gnome_keyring} -BuildRequires: libgnome-keyring-devel -%endif -BuildRequires: pcre-devel -BuildRequires: openssl-devel -BuildRequires: zlib-devel >= 1.2 -%if %{use_systemd} -# For macros -BuildRequires: systemd -%endif -Requires: less -Requires: openssh-clients -Requires: perl(Error) -Requires: perl(Term::ReadKey) -Requires: perl-Git = %{version}-%{release} -Requires: rsync -Requires: zlib >= 1.2 - -Provides: git-core = %{version}-%{release} -Obsoletes: git-core <= 1.5.4.3 +%global gitexecdir %{_libexecdir}/git-core +Name: git +Version: 2.43.0 +Release: 9%{?dist} +Summary: A popular and widely used Version Control System +License: GPLv2+ or LGPLv2.1 +URL: https://git-scm.com/ +Source0: https://www.kernel.org/pub/software/scm/git/%{name}-%{version}.tar.xz +Source1: https://www.kernel.org/pub/software/scm/git/%{name}-%{version}.tar.sign + +Source100: git-gui.desktop +Source101: git@.service.in +Source102: git.socket + +Patch0: backport-send-email-avoid-duplicate-specification-warnings.patch +Patch1: backport-CVE-2024-32002-submodules-submodule-paths-m.patch +Patch2: backport-CVE-2024-32021-builtin-clone-stop-resolving-symlinks-when-copying-f.patch +Patch3: backport-CVE-2024-32021-builtin-clone-abort-when-hardlinked-source-and-targe.patch +Patch4: backport-CVE-2024-32004-t0411-add-tests-for-cloning-from-partial-repo.patch +Patch5: backport-CVE-2024-32004-fetch-clone-detect-dubious-ownership-of-local-reposi.patch +Patch6: backport-CVE-2024-32020-builtin-clone-refuse-local-clones-of-unsafe-reposito.patch +Patch7: backport-CVE-2024-32465-upload-pack-disable-lazy-fetching-by-default.patch +Patch8: backport-CVE-2024-50349-credential_format-also-encode-host-port.patch +Patch9: backport-CVE-2024-50349-credential-sanitize-the-user-prompt.patch +Patch10: backport-CVE-2024-52006-credential-disallow-Carriage-Returns-in-the-protocol.patch +Patch11: backport-CVE-2024-52005.patch +Patch12: backport-CVE-2025-48384-config-quote-values-containing-CR-character.patch +Patch13: backport-CVE-2025-48385-bundle-uri-fix-arbitrary-file-writes-via-parameter-i.patch +Patch14: backport-CVE-2025-48386-wincred-avoid-buffer-overflow-in-wcsncat.patch + +BuildRequires: gcc gettext +BuildRequires: openssl-devel libcurl-devel expat-devel systemd asciidoc xmlto glib2-devel libsecret-devel pcre2-devel desktop-file-utils +BuildRequires: python3-devel perl-interpreter perl-Error perl(Test::More) perl-MailTools perl(Test) +Requires: perl(Term::ReadKey) perl-Git git-core = %{version}-%{release} +Obsoletes: %{name}-subtree < %{version}-%{release} %{name}-p4 < %{version}-%{release} git-cvs < %{version}-%{release} +Provides: %{name} = %{version}-%{release} %{name}-subtree = %{version}-%{release} %{name}-p4 = %{version}-%{release} -# Obsolete git-arch -Obsoletes: git-arch < %{version}-%{release} %description +Git is a free and open source distributed version control system +designed to handle everything from small to very large projects +with speed and efficiency. +Git is easy to learn and has a tiny footprint with lightning fast +performance. It outclasses SCM tools like Subversion, CVS, Perforce, +and ClearCase with features like cheap local branching, convenient +staging areas, and multiple workflows. + +%package core +Summary: Core package of git with minimal functionality +Requires: less +Requires: openssh-clients +Requires: zlib >= 1.2 +%description core Git is a fast, scalable, distributed revision control system with an unusually rich command set that provides both high-level operations and full access to internals. -The git rpm installs the core tools with minimal dependencies. To -install all git packages, including tools for integrating with other -SCMs, install the git-all meta-package. +The git-core rpm installs really the core tools with minimal +dependencies. Install git package for common set of tools. +To install all git packages, including tools for integrating with +other SCMs, install the git-all meta-package -%package all -Summary: Meta-package to pull in all git tools -Group: Development/Tools -BuildArch: noarch -Requires: git = %{version}-%{release} -Requires: git-cvs = %{version}-%{release} -Requires: git-email = %{version}-%{release} -Requires: git-gui = %{version}-%{release} -Requires: git-svn = %{version}-%{release} -Requires: git-p4 = %{version}-%{release} -Requires: gitk = %{version}-%{release} -Requires: perl-Git = %{version}-%{release} -Requires: emacs-git = %{version}-%{release} -Requires: git-instaweb = %{version}-%{release} -Requires: git-gnome-keyring = %{version}-%{release} -Obsoletes: git <= 1.5.4.3 - -%description all -Git is a fast, scalable, distributed revision control system with an -unusually rich command set that provides both high-level operations -and full access to internals. +%package daemon +Summary: Git server daemon +Requires: %{name} = %{version}-%{release} systemd -This is a dummy package which brings in all subpackages. +%description daemon +%{summary}. -%package bzr -Summary: Git tools for working with bzr repositories -Group: Development/Tools + +%package gui +Summary: Graphical user interface to Git BuildArch: noarch -Requires: git = %{version}-%{release} -Requires: bzr +Requires: %{name} = %{version}-%{release} tk -%description bzr +%description gui %{summary}. -%package daemon -Summary: Git protocol dæmon -Group: Development/Tools -Requires: git = %{version}-%{release} -%if %{use_systemd} -Requires: systemd -Requires(post): systemd -Requires(preun): systemd -Requires(postun): systemd -%else -Requires: xinetd -%endif -%description daemon -The git dæmon for supporting git:// access to git repositories -%package -n gitweb -Summary: Simple web interface to git repositories -Group: Development/Tools +%package -n gitk +Summary: TK based graphical tool for visualization of Git BuildArch: noarch -Requires: git = %{version}-%{release} +Requires: %{name} = %{version}-%{release} tk -%description -n gitweb -Simple web interface to track changes in git repositories +%description -n gitk +%{summary}. -%package hg -Summary: Git tools for working with mercurial repositories -Group: Development/Tools + +%package web +Summary: Git web interfaces BuildArch: noarch -Requires: git = %{version}-%{release} -Requires: mercurial +Requires: %{name} = %{version}-%{release} +Obsoletes: gitweb < %{version}-%{release} %{name}-instaweb < %{version}-%{release} +Provides: gitweb = %{version}-%{release} %{name}-instaweb = %{version}-%{release} -%description hg -%{summary}. +%description web +Git web interface allows user browsing git repositories via web service. -%package p4 -Summary: Git tools for working with Perforce depots -Group: Development/Tools +%package svn +Summary: Git tools for importing Subversion repositories BuildArch: noarch -BuildRequires: python -Requires: git = %{version}-%{release} -%description p4 -%{summary}. +Requires: %{name} = %{version}-%{release} subversion perl-TermReadKey perl-Digest-MD5 -%package svn -Summary: Git tools for importing Subversion repositories -Group: Development/Tools -Requires: git = %{version}-%{release}, subversion, perl(Term::ReadKey) %description svn -Git tools for importing Subversion repositories. - -%package cvs -Summary: Git tools for importing CVS repositories -Group: Development/Tools -BuildArch: noarch -Requires: git = %{version}-%{release}, cvs -Requires: cvsps -Requires: perl-DBD-SQLite -%description cvs -Git tools for importing CVS repositories. +%{summary}. %package email -Summary: Git tools for sending email -Group: Development/Tools +Summary: Git tools for sending patches via email BuildArch: noarch -Requires: git = %{version}-%{release}, perl-Git = %{version}-%{release} -Requires: perl(Authen::SASL) -Requires: perl(Net::SMTP::SSL) -%description email -Git tools for sending email. - -%package gui -Summary: Git GUI tool -Group: Development/Tools -BuildArch: noarch -Requires: git = %{version}-%{release}, tk >= 8.4 -Requires: gitk = %{version}-%{release} -%description gui -Git GUI tool. +Requires: git = %{version}-%{release} +Requires: perl-Authen-SASL perl-Net-SMTP-SSL -%package -n gitk -Summary: Git revision tree visualiser -Group: Development/Tools -BuildArch: noarch -Requires: git = %{version}-%{release}, tk >= 8.4 -%description -n gitk -Git revision tree visualiser. +%description email +%{summary}. %package -n perl-Git Summary: Perl interface to Git -Group: Development/Libraries BuildArch: noarch -Requires: git = %{version}-%{release} -BuildRequires: perl(Error), perl(ExtUtils::MakeMaker) -Requires: perl(Error) -Requires: perl(:MODULE_COMPAT_%(eval "`%{__perl} -V:version`"; echo $version)) +Requires: git = %{version}-%{release} perl(:MODULE_COMPAT_%(perl -V:version | cut -d"'" -f 2)) %description -n perl-Git -Perl interface to Git. +%{summary}. %package -n perl-Git-SVN Summary: Perl interface to Git::SVN -Group: Development/Libraries BuildArch: noarch -Requires: git = %{version}-%{release} -Requires: perl(:MODULE_COMPAT_%(eval "`%{__perl} -V:version`"; echo $version)) +Requires: git = %{version}-%{release} perl(:MODULE_COMPAT_%(perl -V:version | cut -d"'" -f 2)) %description -n perl-Git-SVN -Perl interface to Git. - -%package -n emacs-git -Summary: Git version control system support for Emacs -Group: Applications/Editors -Requires: git = %{version}-%{release} -BuildArch: noarch -Requires: emacs(bin) >= %{_emacs_version} - -%description -n emacs-git %{summary}. -%package -n emacs-git-el -Summary: Elisp source files for git version control system support for Emacs -Group: Applications/Editors -BuildArch: noarch -Requires: emacs-git = %{version}-%{release} +%package help +Summary: Man pages and documents for Git system +BuildArch: noarch +Obsoletes: %{name}-core-doc < %{version}-%{release} +Provides: %{name}-core-doc = %{version}-%{release} -%description -n emacs-git-el +%description help %{summary}. -%package instaweb -Summary: Repository browser in gitweb -Group: Development/Tools -BuildArch: noarch -# to get the definition of _httpd_moddir -BuildRequires: httpd-devel -Requires: git = %{version}-%{release} -Requires: gitweb = %{version}-%{release} -Requires: mod_ssl -Requires: httpd +%prep +%autosetup -n %{name}-%{version} -p1 -%description instaweb -A simple script to set up gitweb and a web server for browsing the local repository. +rm -rf perl/Git/LoadCPAN{.pm,/} +grep -rlZ '^use Git::LoadCPAN::' | xargs -r0 sed -i 's/Git::LoadCPAN:://g' -%if %{gnome_keyring} -%package gnome-keyring -Summary: Git module for working with gnome-keyring -BuildRequires: libgnome-keyring-devel -Requires: git = %{version}-%{release} -Requires: gnome-keyring -%description gnome-keyring -%{summary}. -%endif +sed -i '/^git-cvs/d' command-list.txt -%prep -%setup -q -%patch0 -p1 -%patch1 -p1 -%patch5 -p1 -%patch6 -p1 -%patch7 -p1 -%patch8 -p1 -%patch9 -p1 -%patch10 -p1 -%patch11 -p1 -%patch12 -p1 -%patch13 -p1 -%patch14 -p1 -%patch15 -p1 -%patch16 -p1 -%patch17 -p1 -%patch18 -p1 -%patch19 -p1 -%patch20 -p1 -%patch21 -p1 -%patch22 -p1 -%patch23 -p1 -%patch24 -p1 -%patch25 -p1 -%patch26 -p1 -%patch27 -p1 -%patch28 -p1 -%patch29 -p1 - -chmod a+x t/t0011-hashmap.sh t/t1307-config-blob.sh t/t4139-apply-escape.sh t/t7415-submodule-names.sh t/t7416-submodule-dash-url.sh t/t7417-submodule-path-url.sh - -# Use these same options for every invocation of 'make'. -# Otherwise it will rebuild in %%install due to flags changes. -cat << \EOF > config.mak -V = 1 +%build +%configure + +# Some options can not configure in configure script, so give options here (config.mak included in Makefile) +cat > config.mak << EOF CFLAGS = %{optflags} -BLK_SHA1 = 1 +LDFLAGS = %{__global_ldflags} NEEDS_CRYPTO_WITH_SSL = 1 USE_LIBPCRE = 1 -ETC_GITCONFIG = %{_sysconfdir}/gitconfig -DESTDIR = %{buildroot} -INSTALL = install -p -GITWEB_PROJECTROOT = %{_var}/lib/git +INSTALL_SYMLINKS = 1 GNU_ROFF = 1 -htmldir = %{_docdir}/%{name}-%{version} -prefix = %{_prefix} -gitwebdir = %{_var}/www/git +GITWEB_PROJECTROOT = %{_localstatedir}/lib/git +PYTHON_PATH = %{__python3} +htmldir = %{?_pkgdocdir} +perllibdir = %{perl_vendorlib} +gitwebdir = %{_localstatedir}/www/git +NO_PERL_CPAN_FALLBACKS = 1 EOF -%if "%{gitcoredir}" == "%{_bindir}" -echo gitexecdir = %{_bindir} >> config.mak -%endif +# Default using python3 +sed -i -e '1s@#!\( */usr/bin/env python\|%{__python2}\)$@#!%{__python3}@' \ + contrib/hg-to-git/hg-to-git.py +%make_build +%make_build -C contrib/subtree/ +%make_build -C contrib/contacts/ +%make_build -C contrib/credential/libsecret/ +%make_build -C contrib/credential/netrc/ +%make_build -C contrib/diff-highlight/ -# Filter bogus perl requires -# packed-refs comes from a comment in contrib/hooks/update-paranoid -# YAML::Any is optional and not available on el5 -cat << \EOF > %{name}-req -#!/bin/sh -%{__perl_requires} $* |\ -sed -e '/perl(packed-refs)/d' -EOF +%install +%make_install %{_smp_mflags} install-doc +%make_install %{_smp_mflags} -C contrib/subtree/ install-doc +%make_install %{_smp_mflags} -C contrib/contacts/ install-doc -%global __perl_requires %{_builddir}/%{name}-%{version}/%{name}-req -chmod +x %{__perl_requires} +install -p -m 644 README.md %{buildroot}%{_pkgdocdir} +install -p -m 644 gitweb/INSTALL %{buildroot}%{_pkgdocdir}/INSTALL.gitweb +install -p -m 644 gitweb/README %{buildroot}%{_pkgdocdir}/README.gitweb -%build -sh configure --with-c-compiler=gcc -make %{?_smp_mflags} git-daemon LDFLAGS="-pie -Wl,-z,relro,-z,now" CFLAGS="$RPM_OPT_FLAGS -fPIC" -make %{?_smp_mflags} all -o git-daemon -make %{?_smp_mflags} %{?with_docs:doc} +#setup bash completion +install -Dpm 644 contrib/completion/git-completion.bash %{buildroot}%{_datadir}/bash-completion/completions/git +ln -s git %{buildroot}%{_datadir}/bash-completion/completions/gitk + +# install contrib to git-core +mkdir -p %{buildroot}%{_datadir}/git-core/contrib/completion +install -p -m 644 contrib/completion/git-completion.tcsh %{buildroot}%{_datadir}/git-core/contrib/completion/ -make -C contrib/emacs +# install root path for gitweb +mkdir -p %{buildroot}%{_localstatedir}/lib/git -%if %{gnome_keyring} -make -C contrib/credential/gnome-keyring/ -%endif +# install config files +desktop-file-install --dir=%{buildroot}%{_datadir}/applications %{SOURCE100} +install -D -p -m 644 %{SOURCE101} %{buildroot}%{_unitdir}/git@.service +install -D -p -m 644 %{SOURCE102} %{buildroot}%{_unitdir}/git.socket -make -C contrib/subtree/ +install -pm 755 contrib/credential/libsecret/git-credential-libsecret %{buildroot}%{gitexecdir} +install -pm 755 contrib/credential/netrc/git-credential-netrc %{buildroot}%{gitexecdir} -# Remove shebang from bash-completion script -sed -i '/^#!bash/,+1 d' contrib/completion/git-completion.bash +rm -f %{buildroot}%{_bindir}/git-cvsserver +rm -f %{buildroot}%{gitexecdir}/git-cvs* -%install -rm -rf %{buildroot} -make %{?_smp_mflags} INSTALLDIRS=vendor install -o git-daemon -make %{?_smp_mflags} INSTALLDIRS=vendor %{?with_docs:install-doc} -o git-daemon - -%global elispdir %{_emacs_sitelispdir}/git -make -C contrib/emacs install \ - emacsdir=%{buildroot}%{elispdir} -for elc in %{buildroot}%{elispdir}/*.elc ; do - install -pm 644 contrib/emacs/$(basename $elc .elc).el \ - %{buildroot}%{elispdir} -done -install -Dpm 644 %{SOURCE2} \ - %{buildroot}%{_emacs_sitestartdir}/git-init.el - -%if %{gnome_keyring} -install -pm 755 contrib/credential/gnome-keyring/git-credential-gnome-keyring \ - %{buildroot}%{gitcoredir} -# Remove built binary files, otherwise they will be installed in doc -make -C contrib/credential/gnome-keyring/ clean -%endif - -make -C contrib/subtree install -%if %{with docs} -make -C contrib/subtree install-doc -%endif - -mkdir -p %{buildroot}%{_sysconfdir}/httpd/conf.d -install -pm 0644 %{SOURCE4} %{buildroot}%{_sysconfdir}/httpd/conf.d/git.conf -sed "s|@PROJECTROOT@|%{_var}/lib/git|g" \ - %{SOURCE6} > %{buildroot}%{_sysconfdir}/gitweb.conf - -find %{buildroot} -type f -name .packlist -exec rm -f {} ';' -find %{buildroot} -type f -name '*.bs' -empty -exec rm -f {} ';' -find %{buildroot} -type f -name perllocal.pod -exec rm -f {} ';' - -# Remove remote-helper python libraries and scripts, these are not ready for -# use yet -rm -rf %{buildroot}%{python_sitelib} %{buildroot}%{gitcoredir}/git-remote-testgit - -# git-archimport is not supported -find %{buildroot} Documentation -type f -name 'git-archimport*' -exec rm -f {} ';' - -exclude_re="archimport|email|git-citool|git-cvs|git-daemon|git-gui|git-remote-bzr|git-remote-hg|gitk|p4|svn|instaweb|gnome-keyring" -(find %{buildroot}{%{_bindir},%{_libexecdir}} -type f | grep -vE "$exclude_re" | sed -e s@^%{buildroot}@@) > bin-man-doc-files +%find_lang %{name} +cat %{name}.lang >> git-bin-files + +# split out bin for primary package +(find %{buildroot}{%{_bindir},%{gitexecdir}} -type f -o -type l | grep -vE "git-(gui|daemon|cvs|svn|instaweb|citool|send-email)" | \ +sed -e s@^%{buildroot}@@) >> git-bin-files + +exclude_re="archimport|email|git-(citool|credential-libsecret|cvs|daemon|gui|instaweb|p4|subtree|svn)|gitk|gitweb|p4merge" +(find %{buildroot}{%{_bindir},%{_libexecdir}} -type f -o -type l | grep -vE "$exclude_re" | sed -e s@^%{buildroot}@@) > bin-man-doc-files (find %{buildroot}{%{_bindir},%{_libexecdir}} -mindepth 1 -type d | grep -vE "$exclude_re" | sed -e 's@^%{buildroot}@%dir @') >> bin-man-doc-files + (find %{buildroot}%{perl_vendorlib} -type f | sed -e s@^%{buildroot}@@) > perl-git-files (find %{buildroot}%{perl_vendorlib} -mindepth 1 -type d | sed -e 's@^%{buildroot}@%dir @') >> perl-git-files -# Split out Git::SVN files +# split out perl files for Git and Git::SVN grep Git/SVN perl-git-files > perl-git-svn-files sed -i "/Git\/SVN/ d" perl-git-files -%if %{with docs} -(find %{buildroot}%{_mandir} -type f | grep -vE "$exclude_re|Git" | sed -e s@^%{buildroot}@@ -e 's/$/*/' ) >> bin-man-doc-files -%else -rm -rf %{buildroot}%{_mandir} -%endif - -mkdir -p %{buildroot}%{_var}/lib/git -%if %{use_systemd} -mkdir -p %{buildroot}%{_unitdir} -cp -a %{SOURCE12} %{SOURCE13} %{buildroot}%{_unitdir} -%else -mkdir -p %{buildroot}%{_sysconfdir}/xinetd.d -perl -p \ - -e "s|\@GITCOREDIR\@|%{gitcoredir}|g;" \ - -e "s|\@BASE_PATH\@|%{_var}/lib/git|g;" \ - %{SOURCE3} > %{buildroot}%{_sysconfdir}/xinetd.d/git -%endif - -# Install bzr and hg remote helpers from contrib -install -pm 755 contrib/remote-helpers/git-remote-{bzr,hg} %{buildroot}%{gitcoredir} - -# Setup bash completion -mkdir -p %{buildroot}%{_sysconfdir}/bash_completion.d -install -pm 644 contrib/completion/git-completion.bash %{buildroot}%{_sysconfdir}/bash_completion.d/git - -# Install tcsh completion -mkdir -p %{buildroot}%{_datadir}/git-core/contrib/completion -install -pm 644 contrib/completion/git-completion.tcsh \ - %{buildroot}%{_datadir}/git-core/contrib/completion/ - -# Move contrib/hooks out of %%docdir and make them executable -mkdir -p %{buildroot}%{_datadir}/git-core/contrib -mv contrib/hooks %{buildroot}%{_datadir}/git-core/contrib -chmod +x %{buildroot}%{_datadir}/git-core/contrib/hooks/* -pushd contrib > /dev/null -ln -s ../../../git-core/contrib/hooks -popd > /dev/null - -# Install git-prompt.sh -mkdir -p %{buildroot}%{_datadir}/git-core/contrib/completion -install -pm 644 contrib/completion/git-prompt.sh \ - %{buildroot}%{_datadir}/git-core/contrib/completion/ - -# install git-gui .desktop file -desktop-file-install \ -%if %{with_desktop_vendor_tag} - --vendor fedora \ -%endif - --dir=%{buildroot}%{_datadir}/applications %{SOURCE5} - -# find translations -%find_lang %{name} %{name}.lang -cat %{name}.lang >> bin-man-doc-files - -# quiet some rpmlint complaints -chmod -R g-w %{buildroot} -find %{buildroot} -name git-mergetool--lib | xargs chmod a-x -rm -f {Documentation/technical,contrib/emacs,contrib/credential/gnome-keyring}/.gitignore -chmod a-x Documentation/technical/api-index.sh -find contrib -type f | xargs chmod -x + +# Split core files +not_core_re="git-(add--interactive|contacts|credential-netrc|filter-branch|instaweb|request-pull|send-mail)|gitweb" +grep -vE "$not_core_re|%{_mandir}" bin-man-doc-files > bin-files-core +grep -E "$not_core_re" bin-man-doc-files > git-bin-files %check -# Tests to skip on all releases and architectures -# t9001-send-email - Can't locate Data/Dumper.pm in @INC - prbly missing dep +#make %{?_smp_mflags} test GIT_SKIP_TESTS="t9001" - export GIT_SKIP_TESTS - -# Set LANG so various UTF-8 tests are run export LANG=en_US.UTF-8 +#make test || true -make test - -%clean -rm -rf %{buildroot} +%preun daemon +%systemd_preun git.socket -%if %{use_systemd} %post daemon -%systemd_post git@.service - -%preun daemon -%systemd_preun git@.service +%systemd_post git.socket %postun daemon -%systemd_postun_with_restart git@.service -%endif +%systemd_postun_with_restart git.socket -%files -f bin-man-doc-files +%files -f git-bin-files %defattr(-,root,root) +%{_datadir}/git-core/templates/hooks/fsmonitor-watchman.sample +%{_datadir}/git-core/templates/hooks/pre-rebase.sample +%{_datadir}/git-core/templates/hooks/prepare-commit-msg.sample +%{gitexecdir}/git-archimport +%{gitexecdir}/git-credential-libsecret +%{gitexecdir}/git-p4 +%{gitexecdir}/git-subtree +%{gitexecdir}/mergetools/p4merge +%doc README.md +%license LGPL-2.1 COPYING + +%files core -f bin-files-core +%license LGPL-2.1 COPYING +# exclude is best way here because of troubles with symlinks inside git-core/ +%exclude %{_datadir}/git-core/templates/hooks/fsmonitor-watchman.sample +%exclude %{_datadir}/git-core/templates/hooks/pre-rebase.sample +%exclude %{_datadir}/git-core/templates/hooks/prepare-commit-msg.sample +#%{_datadir}/locale/{bg,ca,de,el,es,fr,id,is,it,ko,pl,pt_PT,ru,sv,uk,tr,vi,zh_CN,zh_TW}/LC_MESSAGES/git.mo +%{_datadir}/locale/*/LC_MESSAGES/git.mo +%{_datadir}/bash-completion/completions %{_datadir}/git-core/ -%doc README COPYING Documentation/*.txt Documentation/RelNotes contrib/ -%{!?_without_docs: %doc Documentation/*.html Documentation/docbook-xsl.css} -%{!?_without_docs: %doc Documentation/howto Documentation/technical} -%{_sysconfdir}/bash_completion.d - -%files bzr -%defattr(-,root,root) -%{gitcoredir}/git-remote-bzr -%files hg -%defattr(-,root,root) -%{gitcoredir}/git-remote-hg - -%files p4 -%defattr(-,root,root) -%{gitcoredir}/*p4* -%{gitcoredir}/mergetools/p4merge -%doc Documentation/*p4*.txt -%{?with_docs: %{_mandir}/man1/*p4*.1*} -%{?with_docs: %doc Documentation/*p4*.html } - -%files svn -%defattr(-,root,root) -%{gitcoredir}/*svn* -%doc Documentation/*svn*.txt -%{?with_docs: %{_mandir}/man1/*svn*.1*} -%{?with_docs: %doc Documentation/*svn*.html } - -%files cvs -%defattr(-,root,root) -%doc Documentation/*git-cvs*.txt -%{_bindir}/git-cvsserver -%{gitcoredir}/*cvs* -%{?with_docs: %{_mandir}/man1/*cvs*.1*} -%{?with_docs: %doc Documentation/*git-cvs*.html } - -%files email +%files daemon %defattr(-,root,root) -%doc Documentation/*email*.txt -%{gitcoredir}/*email* -%{?with_docs: %{_mandir}/man1/*email*.1*} -%{?with_docs: %doc Documentation/*email*.html } +%{_unitdir}/git.socket +%{_unitdir}/git@.service +%{gitexecdir}/git-daemon +%{_localstatedir}/lib/git %files gui %defattr(-,root,root) -%{gitcoredir}/git-gui* -%{gitcoredir}/git-citool -%{_datadir}/applications/*git-gui.desktop +%{_datadir}/applications/git-gui.desktop +%{gitexecdir}/git-gui* +%{gitexecdir}/git-citool %{_datadir}/git-gui/ -%{?with_docs: %{_mandir}/man1/git-gui.1*} -%{?with_docs: %doc Documentation/git-gui.html} -%{?with_docs: %{_mandir}/man1/git-citool.1*} -%{?with_docs: %doc Documentation/git-citool.html} %files -n gitk %defattr(-,root,root) -%doc Documentation/*gitk*.txt -%{_bindir}/*gitk* -%{_datadir}/gitk -%{?with_docs: %{_mandir}/man1/*gitk*.1*} -%{?with_docs: %doc Documentation/*gitk*.html } +%{_bindir}/gitk +%{_datadir}/gitk/ -%files -n perl-Git -f perl-git-files -%defattr(-,root,root) -%exclude %{_mandir}/man3/*Git*SVN*.3pm* -%{?with_docs:%{_mandir}/man3/*Git*.3pm*} - -%files -n perl-Git-SVN -f perl-git-svn-files +%files web %defattr(-,root,root) -%{?with_docs:%{_mandir}/man3/*Git*SVN*.3pm*} +%{_pkgdocdir}/*.gitweb +%{_localstatedir}/www/git/ +%{gitexecdir}/git-instaweb -%files -n emacs-git +%files svn %defattr(-,root,root) -%doc contrib/emacs/README -%dir %{elispdir} -%{elispdir}/*.elc -%{_emacs_sitestartdir}/git-init.el +%{gitexecdir}/git-svn -%files -n emacs-git-el +%files email %defattr(-,root,root) -%{elispdir}/*.el +%{gitexecdir}/*email* -%files daemon -%defattr(-,root,root) -%doc Documentation/*daemon*.txt -%if %{use_systemd} -%{_unitdir}/git.socket -%{_unitdir}/git@.service -%else -%config(noreplace)%{_sysconfdir}/xinetd.d/git -%endif -%{gitcoredir}/git-daemon -%{_var}/lib/git -%{?with_docs: %{_mandir}/man1/*daemon*.1*} -%{?with_docs: %doc Documentation/*daemon*.html} - -%files -n gitweb -%defattr(-,root,root) -%doc gitweb/INSTALL gitweb/README -%config(noreplace)%{_sysconfdir}/gitweb.conf -%config(noreplace)%{_sysconfdir}/httpd/conf.d/git.conf -%{_var}/www/git/ +%files -n perl-Git -f perl-git-files +%{_mandir}/man3/Git.* -%files instaweb -%defattr(-,root,root) -%{gitcoredir}/git-instaweb -%{?with_docs: %{_mandir}/man1/git-instaweb.1*} -%{?with_docs: %doc Documentation/git-instaweb.html} +%files -n perl-Git-SVN -f perl-git-svn-files -%if %{gnome_keyring} -%files gnome-keyring +%files help %defattr(-,root,root) -%{gitcoredir}/git-credential-gnome-keyring -%endif - - -%files all -# No files for you! - +%exclude %{_pkgdocdir}/{README.md,*.gitweb} +%{_pkgdocdir}/* +%{_mandir}/man1/git*.1.* +%{_mandir}/man1/scalar*.1.* +%{_mandir}/man5/git*.5.* +%{_mandir}/man7/git*.7.* %changelog +* Thu Jul 10 2025 zhuhongbo - 2.43.0-9 +- fix: fix cve CVE-2025-48384 + * Thu May 18 2023 Masahiro Matsuya - 1.8.3.1-25 - Fixes CVE-2023-25652 and CVE-2023-29007 - Resolves: #2188354, #2188365 diff --git a/git.yaml b/git.yaml new file mode 100644 index 0000000000000000000000000000000000000000..548dc5b35663929608be389878d93fa7f32bd031 --- /dev/null +++ b/git.yaml @@ -0,0 +1,4 @@ +version_control: github +src_repo: git/git +tag_prefix: ^v +seperator: . diff --git a/git@.service.in b/git@.service.in new file mode 100644 index 0000000000000000000000000000000000000000..2b80b95dbd22156d0d0477ff155ad2002892c369 --- /dev/null +++ b/git@.service.in @@ -0,0 +1,10 @@ +[Unit] +Description=Git Repositories Server Daemon +Documentation=man:git-daemon(1) + +[Service] +User=nobody +ExecStart=-/usr/libexec/git-core/git-daemon --base-path=/var/lib/git --export-all \ + --user-path=public_git --inetd --log-destination=stderr --verbose +StandardInput=socket +StandardError=journal diff --git a/gitweb-httpd.conf b/gitweb-httpd.conf new file mode 100644 index 0000000000000000000000000000000000000000..4f4eac7c7615015c59d00df0cc53c5ba22f5d8ca --- /dev/null +++ b/gitweb-httpd.conf @@ -0,0 +1,7 @@ +Alias /git /var/www/git + + + Options +ExecCGI + AddHandler cgi-script .cgi + DirectoryIndex gitweb.cgi + diff --git a/gpgkey-junio.asc b/gpgkey-junio.asc new file mode 100644 index 0000000000000000000000000000000000000000..5edb58f32974f294b3a89837073b4edfb40bc57a --- /dev/null +++ b/gpgkey-junio.asc @@ -0,0 +1,144 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQINBE6GdewBEADE3szNmKeUAUad22z1tWkLjLzyDcJpF7IzEnLs8bD1y0I6iqH0 +169ru5iXKn29wc+YAuxWorb4P5a2i2B/vs32hJy/rXE7dpvsAqlHLSGSDUJXiFzM +Bb9SfJO0EY2r+vqzeQgSUmhp/b4dAXVnMATFM37V83H/mq8REl5Wwb2rxP3pcv6W +F6i51+tPEWIUgo1N74QkR4wdLcPztDO9v7ZIaFKl+2GEGkx6Z+YjECTqQuyushjq +41K3UVmv+AmLhJYKA78HY5KqCkXrz8rCgoi+Ih+ZT2sgjx637yT84Dr/QDh7BkIB +blmpRQ+yoJlVDWI5/bI8rcdrPz+NmxaJ7dKEBg0qTclbwquacpwG1DCCD8NgQrwL +WVLGVdsT2qwek+KkmOs+iNBXY1TgKPAeuv0ZDKKYrCwYpN1K90oXk431g79bKsH5 +8Tybg5uW+e2i+H5gnDeyl481HOt8aHOPu9qIB/zIek6lDH69q3nGcf7k3prxDf3I +qYy6CPcpjTfpN4i/7gxQDNI+AIgbs21EE5Kg1TPUe0XgfdJMtIF+D6wTjbrLtDnn +09Iwz0SfIZR52IrZHxUlFXZFjk10RXYATtdMqEFgYgjYvYXxL9EEr7T5Dgso+qaE +wV0rrg0VDKrf/afrjGOeffumlhBhJnBnns1T+p65Vz5hyQl7SFKLw+Ix7wARAQAB +tCJKdW5pbyBDIEhhbWFubyA8Z2l0c3RlckBwb2JveC5jb20+iQI7BBMBAgAlAhsD +BgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAUCToZ45QIZAQAKCRAg0E5acTZgp1TF +EACr+QRpfDmbGnUY1Rqy50Ap1eG0061vAapCMLmU+4kxqIRKm5/00YGmb7VxRCLD +pKNa0hkH+ftA4QmnPU4j4UEsh/vAa2BGCXRjB9RixTokvQf9iOXUGiHYv1kn+p3l +xg66bLnKV3dWScjV2IueDP4ypLEZHlWD9I/Unmrg2mJEAcz4gSAfBHWLOf/+JYAq +6j6erIxPS5ZtIz/twQf6MCoXXAXuM6tgUhdptJqG82WzSZMuWOfzmS6DSTuqK05h +9gpwdj5nz4jdh4u5sp+LKOqFw94JIRcE+wj5cljOOlX3Fqi84ADC8b/OzC3V9KGa +rNnBzWdnkIoNxbNBNF6wD1dgn1peueufaP9q5CO9ljKNSOGUClwvtJFrpZZL5Phe +NNFFkPSZpkmStcB6s8RHsyz5zuqxQUOWuvLVUDRW58yZR0WC1Xc/yi+cEFSUiKI5 +OqPNwC1v0xh7a/MObJQxTQCEKHLyVYlnohsf2RxzxaOOjgWmY2O+yH5G5ymfBie/ +Uw7zcSsJ89ovLAEG/10tkJVqIfza5Wexj3VAZbI+i7vx2gtlLqM23gGykqcv7VWm +FD5lFWGC4Sw8M7Jikm8vn99dxZnsBKjMqksjENUX1JeUZI+FHg2CNSVBX0J8yLnm +d8eJBkYXkU79J3GVex/WTzbFnSkPmw16MtAu/E9EKNbAILQgSnVuaW8gQyBIYW1h +bm8gPGp1bmlvQHBvYm94LmNvbT6JAjgEEwECACIFAk6GeL4CGwMGCwkIBwMCBhUI +AgkKCwQWAgMBAh4BAheAAAoJECDQTlpxNmCn6GMQAJ0V0jmyQ7Lvi5FBBgNTdY8q +fVbLFxEUVAsKf2x9QxhsOcL2heQRVkp10JKv4/VQLfDwr6Pv98FQchXlBmFiySAb +VihUVC+VJ3FhyKBtI14RXT6Nkwd18PXDvWXy2fKeiK9GPDWkufac0h/giz0T1xP7 +CHxDErQATMmYbkinyyM+xd1Nir6DUYcHJQIK2Dg2VPChkI0XXCQETLDbrC9fDwWg +1vP36PQZ+nw/cIRt+2xkq8HHUzB7kOnXHqPt1kb/Ry8hZwPnfV7g/V0MogoMLtz2 +33pqwuguLXP7zY3jTwAZZ9VTpuCTsdVWXJDlznMNurYi1yurCNuUvq/O/9JC8WBt +dVUuvFZGjRZWfP24W57iq/qz8CV6dThq5r4WygE83tMC3DaarNJ4f9dQUA4KpL7j +2EMXkgoXcEy1mieUCypdNiZj96hV8Q7apSLk2V4jtvLkJfzX053glqRJI35SX8Ok +SazZGYZHX6QfZlvznnrCF5x/xBzhbfr2Geo4rxL0BQsp2DQodqUCB23QzsPhWWff +YtkATaD5vovGeQ9Acd1u72jH3DO8tVMH85jMO4f+oc0h3lnkPS4F33QqlnErRo/I +Rm6jCsI/NgMZUYdh0EY5Iiq/e8e+u8gdo0akkwHlNvR4KrYrK/1K4h+i+UBIbJDZ +pqT/iH+yhJRQ3CAan8KStB9KdW5pbyBDIEhhbWFubyA8amNoQGdvb2dsZS5jb20+ +iQI4BBMBAgAiBQJOhnjVAhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRAg +0E5acTZgp4SyD/9slQ1IkYqz+VXPnmHCQFhurYcHD8t1iGBqiXxI+gpA1Y3L1QL+ +aj0fplW4KuEPbJ7xlYdLA4J+M9kgkwt3Jufw+lM1pQM9tSB627rAbxUyczj4AFjZ +9v8GpqyZ3XPDe8NknI/V4Xlhsr+e3AHJPr355XacMkFGc3Rtw1quFVgrECttdzUD +6xtrhwYYVAYAnKr65943UtMLsVXkJLfjq8c1NZOCov9SwSb0N9IkEhSyihd/92Z2 +NH4d+B1QTIyWagL3GNN8LXXEHK+x+oA/nbhGbFg7bqhxUW4d2JaxKPy4U3nfdtSm +Mbiy16eUfMbbMyvB0jtLf6UFrxF5bJnYkiG18DcLSaX7Hsby8IVzZQZHYvkx5+7p +K2SBsdek3bu3punP3dWLJoMw+Vmm5Bk0Yl7pxzvsYQWhPV7+tpgglUSFQuIeXFrw +jVXP8Q+Ph9nO0vKIaeTcn1ISuq2XaoqhkLH+Zw1I/ruRtk2DJbZsg5BBGfA26BkZ +WJXlO6h33emPwkJ0FanlzRtMTqZ/4RiTXv5G1L/lypX1iq6fF2V+WTh2JmEKyY+2 +l0/19XRANfaDiYULoBvJEdCcIXLbaRTqjem+70ZGvAiCaGO52YvUhBo+XCgjucjc +qhxiF3wc24kzj1ZycrwbDa7VjftZAApN01CJ38mXGpZXiWZU4hjJx41wCbkCDQRO +iUo5ARAA8l5PToapmK0IHBpY5ohie53ZczLV5ojWKZXNsmVYNuSBBKpwC6VH2X85 +9dVd59HigAYsS1TbDCUNGC1bM0thJ9Y92fa1WnlEqyYQZDmJ4rt283DT2Gmrkng6 +XPjvr8PZeHKtvw7uLywfdm4x0WrGrH34g17BL82u/7k0JUOgJoPulIkO9Mls35UJ +SY/Zwk1EdkM4hHKmqJFIiW/DlPYh0Tj5x9Sukk0ATH/R/QdtpjvwJJZyph6gMhbi +YB+G+nR/WZy9vB+bFwPPaa0EudADoIZ9LkQzU/55KqNnKH9dPqPVWEOBZVZvPqiR +iyRuffMIJ0t9mtvc/jruS1qiTZdJoy2vl6K4Uqc+huvlHeCCYR0lGCeDB+Ixuz9x +d2ZdUxMgwgcNiQOCW70YWtxf0LF2seSJdLItHDBOu/f3cqKwNGUvcC3d/9qVb0wP +SI1mq18S02MGcvDySsjGtX7o4kujUqE2ZNCW6ORLJUC6zEYu3TRNWrXeS3uAP21x +UrEPkuTiJL7SCS12FYJt5agx5NIUKI7bkIUbLbiuhC4z47MFajW9Y5jUQk86dk7b +jGqVrXYIu92Dhxc2CND2fWaMpYRhwvHR6KQU1yYHYkGVlMHiozM5D+4dCRRVI8x3 +p/+ypFBZmZr7yTpv/qD0N8HHl2NAYvGRQdzjyFQOXERwaXuzjCkAEQEAAYkEWwQY +AQoAJgIbAhYhBJbgevJXcZVZgNrRACDQTlpxNmCnBQJeHMcfBQkenRjmAinBXSAE +GQECAAYFAk6JSjkACgkQsLXohpav5sukpRAAywCaKmo0HH77yNkqormnKtRBrz8j +tx68e//pq/AyCrghKUh91iLGYji3/E1qQe7p7Ne7WAn3uFZs22zrNKIDGxtMMCQT +C0Ne4BAvMh1NzwzzBCCyirs1ccLj5gKkoFkKfTo5U5NWNznYPM8uib1uY5vdRqIJ +2vJ7JJykNdcW5od42TtWsOxH2zTp4SRNmX8QPaRbfOxPdlKsbp0eIO6kk+Lx6gEv +WAtEda5xSd1PwyK7SfGadTm+8Rw5UeP1kRtuKQPm7sRBB0coXDVHpFi/nMWHzVxv +/NKhLAkzIbGOV6rL8ihVhXGqEgiD5Q+QdbaNsiLtHo5niBzpbnzvSopBYcOftrhc +PNDY0RYXYb/5JZUid/JBWKwV+zREEnbgtsYDbwFEDnCVIGyXAoxyas/S3b14izat +qgINxiYuxpDY+w1O5RywjOTdLPUWlL5YhH1W/gwbdyGiL4sh0v/fzNy0vKR5zPt1 +hICEA9YvCI7k3b74O6eiDB5fMIRPkNr6ubZWe0T6x4eL2EjSFRXIEmbmnAh93pdp +WFrXH+Sf1LKhBZzojgUsQU/rzB2R94S7Vx0Z+tzgDZ8fJe47ZUEfzJccyyGve/QA +sLLgTWRwRP3MSa1rC4wuWtDDMk/drw9CpmeFeRFn0oDIBo/m2mBv+UNAxSdijREz +vPRiwROma/RawVcJECDQTlpxNmCnTLQP/A1WNmgPCCyFqp812Zvgh0pAqceaM+dg +FlvNi5j5Jyw7/hicx2e0BXgKt64TEodphknCFzZIFDq3jJSdLt1l9NHpiLVM0Hf0 +cLFGF3eRHOID7PeGJGztLJ0CGhhSXaPh7nNLK0G9zXCAasedpowX4ZUntv+p/+Fr +jQ8eSgyyljvrlywK+tH07F1W6t6eMNOw7/AHx7fkOux4CDem1FsNbhZWX8YPUATo +vP1YLBXcrQgpJPpypG6up56D70ewTs4l+qNOISr3phG2egeEhYNwv6GUv8aelh69 +iaUHscT+DOXrFKq+RSHBMzGFFTrDJFDSu3d3A5Rg8KxJMcOxc00L3GMPchrFiJH7 +QShAQdU/ocF0MAA6n56g/QynxafFI/MRMXVTmF+lMBW/kK63pD3AJkIgvdLdht5o +s7aKlddPrmIulaELIDdF2MSicMmgWJcqFkqZH2HIC+gx26Fafn2vfiUqsEc4NTpZ +qhf66F9UjPKfYFfLhbGrmq/giAk1qjiGnBzCUQ9hXVqpmFfnVDjmQrk8KB9skDms +PJgZ4hzmj5AarCpFtDmE4W7Tvi/xqgrFZkPX/SDhTWInJGcWaOTvlc5dkjAxKT6X +LUGLScJHxhaovTGVzq1GWhhNCFhCs4AkWqPKhYfeZuWiuiMLZaEyJPfTufT7Svab +pOhlaD1YY8fvuQINBE6GdewBEADxm56jO5pnVRH13BsG38o1qD9mJppXhf0mb6dB +ORP1b3YJNaknQtxVPXSlXNAYNStYs9bWwn+RrYmOEfy0MWekqOBqgHDEf50ktZaz +hFd89dt58IA+WIFo7BFk1XIr4USdSEQeL7Pb4oSg5AYn8C3OlT7T3nxWBh9aEbat +EfiUMFKikLVVLdbEL7FBzEkypHfQCslDlq+ggAAVBzqrMIBn/idto87UrF2x/qd2 +P2PJl9pUf744pL9yzX+cNbQld0Yf6gQW9/r0UUW/CCU4qpPDvycyGIx3Y7PV/MjA +lre4qJv4khoSFasAAjDXzyUIYhw7yMmaAE/lEOVN7M6reYDvhaDCcWfEn8sjH03/ +Wa92vVx7boMx5RAEh8YE2KZHEZkAODlW4pnDKyaH38lj8pa0dh77RXAD6X1XPGwi +zpmjfrBBPGvUNGsdIpJaY4KEaZ0+v3bhvfU0DWB4dmJB3aPxC6CFtVA0QBGcbw16 +jUeA+2LUJgWMs86npHaPzD99J4Q+Smw9mZPfyT5O5yymYXOwIp50aUjkGCQcHtt7 +jisNkU52bFD2JcQJr8o67JIcqFNdhPAnxC+BN0QDtCyXT+wxC1Uvh9E//r3JPEQD +REfEUb3l+3Sarz1KCm3LUhx1XE82Z6c96tHopUfiOiwbtxv+8UypXT2ntKfprz1U +dMb5jwARAQABiQIfBBgBAgAJBQJOhnXsAhsMAAoJECDQTlpxNmCnFKYP/j6dmEQW +ZliWE8le9Qzh1WqTbHd5elaGJuW0KGQ+g9okWBkh+sLlPxxTk2f0b79Pc7K3OPy7 +89OcIsrbHD3jDp7TS9IVpX7kVZnvnts5oV3XcK5q84XDEQqa6UIlfiZkZJCzIX8N +kSAbv0UmmKKLKS+ANIEIZBKBrWxpYwvG2wBoWPkpNv5mdEuR9h3pZ1aCSZRXysMl +WXo5cMYuZUhabrOqTNP5efEm8iBREHzNSotsiOhHuu7OIPmvZJTUjMrR1wZMCw+Y +uNO2kT3t+ZFTxCx2aeRzqnI55LYFQVBpgSsap/seqRZfj7j7SBb2bSbCuhNedbAw +b3kDWSfJGy/IN6vPdsc3NdsYFK+X8cnypCu4pZDK2IU+CkVrq/ukR8TNdrpAYfEY +XbLq0XFOT0s4jIcjf3dAtlGW36hA0AKPw1BL3cyEGfv2sq75gkw1/jIYMXGc8URJ +y5AfgELIrO1dIjMsm6vFFLeHpAobEP87UEpqIyJtwEIfWdcV5YHYmlFkGd21Lnxp +f2dBAh5dc4MJpYmFZGScSDtTcYCDEXICTgedVOt4WCaV5mwpPeSEzr2TOVm6d1nU +lGBJCV6QPMEdyx03hRkwaTMth0D/SYCvUrjlGQ1VC4WuTveSBhTH7iDrjGSoXNJu +P2Oq+jb/iAfZxuetjpKFD6TCMR0Bcs/cEZuXuQINBFQduiABEACYnNg+kGmtkPmt +kQ/75P8lLsljMk9IIwXGmnFILLpHBM/tN+7wGDxODLY/pPZ2Qfmp7PZLr5Ok5Qnt +v/g+YCtVaTu5Cajt2TOsyH+AYDqtrjjHIt8d2kVloq79ONsCUojFtbFD1nf5W9Sk +WQgntHYRYY1MaCkNd3oUp74TQugzk8Q6UBDamAn1r4nfm6QNXstItqyWsCgQhixW +Qi4WzQc4iA/83t+qUJ+32smjk6J+rGUbbEH8zTASXmcDWYBuPgjo3YEjV+3/qNar +zncYneJfQXwFSgvcR9oUuBQ3ydWJd7sfiImuAnQdRfEC/JFb0iR9sJ395Pw5WQfM +Esrp0uL/Uig52mSrFyIfanxhrJP4j+CyCcJp1TaFINag5/YwHX3GzoikwXUukb+h +KxXxK9Vu8Eu2gAlKFaHt2x5Sc3D1d+nr2QyMkIThC6/d3+XUjgOIMWkCK5dgkuz6 +rs60cRQr8YBGf4Jgk/Xrkk/SjBjBlcTz9lrC06wBRCsa+0XxCAHlM7gVp0HvMn+h +Kx9ny7dPqaqhg8WXuBL0n8yAXXDSgDAin55mRbiKq2bNuMaEJvwKNFU6ENHGSngT +w/Pt6B0dbeB1SBVxJPGbGmk74BL8m5V67Kb7MDP05OLSZsUyNLQCpfSgYsUA14uV +GHE/vE6haP9/DwMLdyJ/CxSjQJMk+wARAQABiQRbBBgBCgAmAhsCFiEEluB68ldx +lVmA2tEAINBOWnE2YKcFAl4cxyAFCRkIqP8CKcFdIAQZAQIABgUCVB26IAAKCRB1 +lO7Hs/fKyah/D/wJ3v4WdqGo7KgW0kmWfFVWZLKwtb+16gcy6nIm7F7VUcODv+qR +LA/4UUg72yabVCXnMBi/eEHtkVZWlB/+tzg643DiRvXTCZiwoS5c6fTze55e/Z87 +qY7okf40aTR+qWuMgligI/LeXunr1Pu2jlJLMcUVh5QLxLZ8bDqpDgQM9zcdFmKQ +/ofUnK7y6gYyUl2KYJDYi0alzjTm+73/S0Mc7z08Yp/s+dtKPbU9imKCnNRkPTQp +cwlYHWJv0YPQ0TdOkid6HJC7CmZEPH845D+qojAjYBPogNIj/RaByaT3kN32zu8+ +jaZJSCnBM0l2lSh/qO7sQBZhqPX5pJDjjj7d/ATY7XxJCnK/2cZVSuVhMXPIFIAQ +G4ZYFUaQssjQKLN7BXJUo7+ec1AMkTiwDUocPza8h+fitcpOsWWJWWvZvkSObbuP +KGn7BgoTzEehO2Rz0QsNjgOa5SXxmc0zX7sbB1XiMxSe7gBZBOnYjhPVcidO3tWu +M/jXGfZAL9ISq6Zf47ebXA7Y+6Bx3oquMgtSN10gbdoJvjqEBJNN65wadvBP8+Sr +L+nWRGhsfmu8jupXdJe8h8ysXCboVkpXHuSu+lDjeL9WLqpwc/XkaOy7B6PfwIRa +YYHnsKs8ogvDuTRJPV4khizyt+A6aiQ1PQqxSKWGY+lzxbmBkPhp5v1N5wkQINBO +WnE2YKdkRQ//ZKvUegOZTtfivAZI888o4Ocpig3CFxJGlXa52JUnDhYFFpRtXRTP +gIdQ0zBvhNjmBnELNv5/D1ubnjqWBTaJpZgUXIljJufuWL7VdD57nAAMw2VLvNUe +38iytUYTAPevaJtLQ4jfj3E9MYH4tcMBmlZ75ZKqiHHH+7+V5J8TD/S01xROK7H1 +kGkXo49deB7K9oT4uno8kE5+AgmEMI80XiKjfQkh6tiG5I0W58DLeAOIxCRkm3kH +Bi22PpuAKhRelRQnAF9dLdlhZECy5eYl7JKQzOS/dQ0Z3zg+HuDBRyhrmV/go/9C +npFGUZBa+FOC1GMO07GKH8tZY99D5tDCAH6r6S+RrYS690mWpjXhqouBtJezld+X +dsgKwgKHk3IEM4m916O0E75kiNk/AD7vZowwEBvPsgN+CDXCPgH4J5x0p9uyxnKH +omLBd7cuJpio6gf4O1KTl1tlVGcb8f+AUR/MIe70NXyEtpYWMiPW3/0dKwt9APgW +KSX0c8Mp2XKH/vAEDx86XTfBNrnXyUanOQhbLQciYzolJjiPrB0C2NgFFFXSHPwC +ikyT5n2RehAJVmg3eufB1ZOKQgo7ue3ynkW4JidgyCUtsoYSmipl9Nhw1hA3ZNK1 +FVCx7tcmy0ZHFO+PV+p17oAC8ZCxSRE0oTeHKcgpF5+DRhQM/+UnmKg= +=7hTI +-----END PGP PUBLIC KEY BLOCK----- diff --git a/print-failed-test-output b/print-failed-test-output new file mode 100644 index 0000000000000000000000000000000000000000..68fa9eec06c0bdd2cfd1cef9aa9178c24138d730 --- /dev/null +++ b/print-failed-test-output @@ -0,0 +1,13 @@ +#!/bin/bash + +shopt -s failglob + +# Print output from failing tests +dashes=$(printf "%80s" '' | tr ' ' '-') +for exit_file in t/test-results/*.exit; do + [ "$(cat "$exit_file")" -eq 0 ] && continue + out_file="${exit_file%exit}out" + printf '\n%s\n%s\n%s\n' "$dashes" "$out_file" "$dashes" + cat "$out_file" +done +exit 1