Blame SOURCES/git-2.17.15-CVE-2020-11008.patch

453c5b
From 692455ed04743fbf2c102ae4bcaa0ac7bcc433a0 Mon Sep 17 00:00:00 2001
453c5b
From: Christian Couder <chriscool@tuxfamily.org>
453c5b
Date: Sun, 1 Dec 2013 08:49:16 +0100
453c5b
Subject: [PATCH 01/11] strbuf: introduce starts_with() and ends_with()
453c5b
453c5b
prefixcmp() and suffixcmp() share the common "cmp" suffix that
453c5b
typically are used to name functions that can be used for ordering,
453c5b
but they can't, because they are not antisymmetric:
453c5b
453c5b
        prefixcmp("foo", "foobar") < 0
453c5b
        prefixcmp("foobar", "foo") == 0
453c5b
453c5b
We in fact do not use these functions for ordering.  Replace them
453c5b
with functions that just check for equality.
453c5b
453c5b
Add starts_with() and end_with() that will be used to replace
453c5b
prefixcmp() and suffixcmp(), respectively, as the first step.  These
453c5b
are named after corresponding functions/methods in programming
453c5b
languages, like Java, Python and Ruby.
453c5b
453c5b
In vcs-svn/fast_export.c, there was already an ends_with() function
453c5b
that did the same thing. Let's use the new one instead while at it.
453c5b
453c5b
Signed-off-by: Christian Couder <chriscool@tuxfamily.org>
453c5b
Signed-off-by: Junio C Hamano <gitster@pobox.com>
453c5b
---
453c5b
 git-compat-util.h     |  2 ++
453c5b
 strbuf.c              | 18 ++++++++++++++++++
453c5b
 vcs-svn/fast_export.c | 11 +----------
453c5b
 3 files changed, 21 insertions(+), 10 deletions(-)
453c5b
453c5b
diff --git a/git-compat-util.h b/git-compat-util.h
453c5b
index bd62ab07a8..c4f0afc104 100644
453c5b
--- a/git-compat-util.h
453c5b
+++ b/git-compat-util.h
453c5b
@@ -341,7 +341,9 @@ extern void set_die_routine(NORETURN_PTR void (*routine)(const char *err, va_lis
453c5b
 extern void set_error_routine(void (*routine)(const char *err, va_list params));
453c5b
 extern void set_die_is_recursing_routine(int (*routine)(void));
453c5b
 
453c5b
+extern int starts_with(const char *str, const char *prefix);
453c5b
 extern int prefixcmp(const char *str, const char *prefix);
453c5b
+extern int ends_with(const char *str, const char *suffix);
453c5b
 extern int suffixcmp(const char *str, const char *suffix);
453c5b
 
453c5b
 static inline const char *skip_prefix(const char *str, const char *prefix)
453c5b
diff --git a/strbuf.c b/strbuf.c
453c5b
index 1170d01c43..83caf4a914 100644
453c5b
--- a/strbuf.c
453c5b
+++ b/strbuf.c
453c5b
@@ -1,6 +1,15 @@
453c5b
 #include "cache.h"
453c5b
 #include "refs.h"
453c5b
 
453c5b
+int starts_with(const char *str, const char *prefix)
453c5b
+{
453c5b
+	for (; ; str++, prefix++)
453c5b
+		if (!*prefix)
453c5b
+			return 1;
453c5b
+		else if (*str != *prefix)
453c5b
+			return 0;
453c5b
+}
453c5b
+
453c5b
 int prefixcmp(const char *str, const char *prefix)
453c5b
 {
453c5b
 	for (; ; str++, prefix++)
453c5b
@@ -10,6 +19,15 @@ int prefixcmp(const char *str, const char *prefix)
453c5b
 			return (unsigned char)*prefix - (unsigned char)*str;
453c5b
 }
453c5b
 
453c5b
+int ends_with(const char *str, const char *suffix)
453c5b
+{
453c5b
+	int len = strlen(str), suflen = strlen(suffix);
453c5b
+	if (len < suflen)
453c5b
+		return 0;
453c5b
+	else
453c5b
+		return !strcmp(str + len - suflen, suffix);
453c5b
+}
453c5b
+
453c5b
 int suffixcmp(const char *str, const char *suffix)
453c5b
 {
453c5b
 	int len = strlen(str), suflen = strlen(suffix);
453c5b
diff --git a/vcs-svn/fast_export.c b/vcs-svn/fast_export.c
453c5b
index f2b23c81de..bd0f2c2b86 100644
453c5b
--- a/vcs-svn/fast_export.c
453c5b
+++ b/vcs-svn/fast_export.c
453c5b
@@ -162,22 +162,13 @@ static void die_short_read(struct line_buffer *input)
453c5b
 	die("invalid dump: unexpected end of file");
453c5b
 }
453c5b
 
453c5b
-static int ends_with(const char *s, size_t len, const char *suffix)
453c5b
-{
453c5b
-	const size_t suffixlen = strlen(suffix);
453c5b
-	if (len < suffixlen)
453c5b
-		return 0;
453c5b
-	return !memcmp(s + len - suffixlen, suffix, suffixlen);
453c5b
-}
453c5b
-
453c5b
 static int parse_cat_response_line(const char *header, off_t *len)
453c5b
 {
453c5b
-	size_t headerlen = strlen(header);
453c5b
 	uintmax_t n;
453c5b
 	const char *type;
453c5b
 	const char *end;
453c5b
 
453c5b
-	if (ends_with(header, headerlen, " missing"))
453c5b
+	if (ends_with(header, " missing"))
453c5b
 		return error("cat-blob reports missing blob: %s", header);
453c5b
 	type = strstr(header, " blob ");
453c5b
 	if (!type)
453c5b
-- 
453c5b
2.26.2
453c5b
453c5b
453c5b
From 6a8afa912b4d2fe5169fbb904537d1a50a3eb66f Mon Sep 17 00:00:00 2001
453c5b
From: Jeff King <peff@peff.net>
453c5b
Date: Thu, 12 Mar 2020 01:31:11 -0400
453c5b
Subject: [PATCH 02/11] credential: detect unrepresentable values when parsing
453c5b
 urls
453c5b
453c5b
The credential protocol can't represent newlines in values, but URLs can
453c5b
embed percent-encoded newlines in various components. A previous commit
453c5b
taught the low-level writing routines to die() when encountering this,
453c5b
but we can be a little friendlier to the user by detecting them earlier
453c5b
and handling them gracefully.
453c5b
453c5b
This patch teaches credential_from_url() to notice such components,
453c5b
issue a warning, and blank the credential (which will generally result
453c5b
in prompting the user for a username and password). We blank the whole
453c5b
credential in this case. Another option would be to blank only the
453c5b
invalid component. However, we're probably better off not feeding a
453c5b
partially-parsed URL result to a credential helper. We don't know how a
453c5b
given helper would handle it, so we're better off to err on the side of
453c5b
matching nothing rather than something unexpected.
453c5b
453c5b
The die() call in credential_write() is _probably_ impossible to reach
453c5b
after this patch. Values should end up in credential structs only by URL
453c5b
parsing (which is covered here), or by reading credential protocol input
453c5b
(which by definition cannot read a newline into a value). But we should
453c5b
definitely keep the low-level check, as it's our final and most accurate
453c5b
line of defense against protocol injection attacks. Arguably it could
453c5b
become a BUG(), but it probably doesn't matter much either way.
453c5b
453c5b
Note that the public interface of credential_from_url() grows a little
453c5b
more than we need here. We'll use the extra flexibility in a future
453c5b
patch to help fsck catch these cases.
453c5b
---
453c5b
 credential.c           | 36 ++++++++++++++++++++++++++++++++++--
453c5b
 credential.h           | 16 ++++++++++++++++
453c5b
 t/t0300-credentials.sh | 12 ++++++++++--
453c5b
 3 files changed, 60 insertions(+), 4 deletions(-)
453c5b
453c5b
diff --git a/credential.c b/credential.c
453c5b
index 096cc5efe9..f56a0a2c02 100644
453c5b
--- a/credential.c
453c5b
+++ b/credential.c
453c5b
@@ -317,7 +317,22 @@ void credential_reject(struct credential *c)
453c5b
 	c->approved = 0;
453c5b
 }
453c5b
 
453c5b
-void credential_from_url(struct credential *c, const char *url)
453c5b
+static int check_url_component(const char *url, int quiet,
453c5b
+			       const char *name, const char *value)
453c5b
+{
453c5b
+	if (!value)
453c5b
+		return 0;
453c5b
+	if (!strchr(value, '\n'))
453c5b
+		return 0;
453c5b
+
453c5b
+	if (!quiet)
453c5b
+		warning(_("url contains a newline in its %s component: %s"),
453c5b
+			name, url);
453c5b
+	return -1;
453c5b
+}
453c5b
+
453c5b
+int credential_from_url_gently(struct credential *c, const char *url,
453c5b
+			       int quiet)
453c5b
 {
453c5b
 	const char *at, *colon, *cp, *slash, *host, *proto_end;
453c5b
 
453c5b
@@ -331,7 +346,7 @@ void credential_from_url(struct credential *c, const char *url)
453c5b
 	 */
453c5b
 	proto_end = strstr(url, "://");
453c5b
 	if (!proto_end)
453c5b
-		return;
453c5b
+		return 0;
453c5b
 	cp = proto_end + 3;
453c5b
 	at = strchr(cp, '@');
453c5b
 	colon = strchr(cp, ':');
453c5b
@@ -366,4 +381,21 @@ void credential_from_url(struct credential *c, const char *url)
453c5b
 		while (p > c->path && *p == '/')
453c5b
 			*p-- = '\0';
453c5b
 	}
453c5b
+
453c5b
+	if (check_url_component(url, quiet, "username", c->username) < 0 ||
453c5b
+	    check_url_component(url, quiet, "password", c->password) < 0 ||
453c5b
+	    check_url_component(url, quiet, "protocol", c->protocol) < 0 ||
453c5b
+	    check_url_component(url, quiet, "host", c->host) < 0 ||
453c5b
+	    check_url_component(url, quiet, "path", c->path) < 0)
453c5b
+		return -1;
453c5b
+
453c5b
+	return 0;
453c5b
+}
453c5b
+
453c5b
+void credential_from_url(struct credential *c, const char *url)
453c5b
+{
453c5b
+	if (credential_from_url_gently(c, url, 0) < 0) {
453c5b
+		warning(_("skipping credential lookup for url: %s"), url);
453c5b
+		credential_clear(c);
453c5b
+	}
453c5b
 }
453c5b
diff --git a/credential.h b/credential.h
453c5b
index 0c3e85e8e4..2846e47cc8 100644
453c5b
--- a/credential.h
453c5b
+++ b/credential.h
453c5b
@@ -27,7 +27,23 @@ void credential_reject(struct credential *);
453c5b
 
453c5b
 int credential_read(struct credential *, FILE *);
453c5b
 void credential_write(const struct credential *, FILE *);
453c5b
+
453c5b
+/*
453c5b
+ * Parse a url into a credential struct, replacing any existing contents.
453c5b
+ *
453c5b
+ * Ifthe url can't be parsed (e.g., a missing "proto://" component), the
453c5b
+ * resulting credential will be empty but we'll still return success from the
453c5b
+ * "gently" form.
453c5b
+ *
453c5b
+ * If we encounter a component which cannot be represented as a credential
453c5b
+ * value (e.g., because it contains a newline), the "gently" form will return
453c5b
+ * an error but leave the broken state in the credential object for further
453c5b
+ * examination.  The non-gentle form will issue a warning to stderr and return
453c5b
+ * an empty credential.
453c5b
+ */
453c5b
 void credential_from_url(struct credential *, const char *url);
453c5b
+int credential_from_url_gently(struct credential *, const char *url, int quiet);
453c5b
+
453c5b
 int credential_match(const struct credential *have,
453c5b
 		     const struct credential *want);
453c5b
 
453c5b
diff --git a/t/t0300-credentials.sh b/t/t0300-credentials.sh
453c5b
index acc167c774..88bcf6534b 100755
453c5b
--- a/t/t0300-credentials.sh
453c5b
+++ b/t/t0300-credentials.sh
453c5b
@@ -289,9 +289,17 @@ test_expect_success 'http paths can be part of context' '
453c5b
 	EOF
453c5b
 '
453c5b
 
453c5b
-test_expect_success 'url parser rejects embedded newlines' '
453c5b
-	test_must_fail git credential fill <<-\EOF
453c5b
+test_expect_success 'url parser ignores embedded newlines' '
453c5b
+	check fill <<-EOF
453c5b
 	url=https://one.example.com?%0ahost=two.example.com/
453c5b
+	--
453c5b
+	username=askpass-username
453c5b
+	password=askpass-password
453c5b
+	--
453c5b
+	warning: url contains a newline in its host component: https://one.example.com?%0ahost=two.example.com/
453c5b
+	warning: skipping credential lookup for url: https://one.example.com?%0ahost=two.example.com/
453c5b
+	askpass: Username:
453c5b
+	askpass: Password:
453c5b
 	EOF
453c5b
 '
453c5b
 
453c5b
-- 
453c5b
2.26.2
453c5b
453c5b
453c5b
From 3b80da45e70d60a4abc91acc45d23d18092708a4 Mon Sep 17 00:00:00 2001
453c5b
From: Jeff King <peff@peff.net>
453c5b
Date: Wed, 11 Mar 2020 18:48:24 -0400
453c5b
Subject: [PATCH 03/11] fsck: detect gitmodules URLs with embedded newlines
453c5b
453c5b
The credential protocol can't handle values with newlines. We already
453c5b
detect and block any such URLs from being used with credential helpers,
453c5b
but let's also add an fsck check to detect and block gitmodules files
453c5b
with such URLs. That will let us notice the problem earlier when
453c5b
transfer.fsckObjects is turned on. And in particular it will prevent bad
453c5b
objects from spreading, which may protect downstream users running older
453c5b
versions of Git.
453c5b
453c5b
We'll file this under the existing gitmodulesUrl flag, which covers URLs
453c5b
with option injection. There's really no need to distinguish the exact
453c5b
flaw in the URL in this context. Likewise, I've expanded the description
453c5b
of t7416 to cover all types of bogus URLs.
453c5b
---
453c5b
 fsck.c                        | 16 +++++++++++++++-
453c5b
 t/t7416-submodule-dash-url.sh | 18 +++++++++++++++++-
453c5b
 2 files changed, 32 insertions(+), 2 deletions(-)
453c5b
453c5b
diff --git a/fsck.c b/fsck.c
453c5b
index 1ed7172ee6..b72faea0c2 100644
453c5b
--- a/fsck.c
453c5b
+++ b/fsck.c
453c5b
@@ -8,6 +8,7 @@
453c5b
 #include "fsck.h"
453c5b
 #include "hashmap.h"
453c5b
 #include "submodule.h"
453c5b
+#include "credential.h"
453c5b
 
453c5b
 struct oidhash_entry {
453c5b
 	struct hashmap_entry ent;
453c5b
@@ -419,6 +420,19 @@ static int fsck_tag(struct tag *tag, const char *data,
453c5b
 	return 0;
453c5b
 }
453c5b
 
453c5b
+static int check_submodule_url(const char *url)
453c5b
+{
453c5b
+	struct credential c = CREDENTIAL_INIT;
453c5b
+	int ret;
453c5b
+
453c5b
+	if (looks_like_command_line_option(url))
453c5b
+		return -1;
453c5b
+
453c5b
+	ret = credential_from_url_gently(&c, url, 1);
453c5b
+	credential_clear(&c);
453c5b
+	return ret;
453c5b
+}
453c5b
+
453c5b
 struct fsck_gitmodules_data {
453c5b
 	struct object *obj;
453c5b
 	fsck_error error_func;
453c5b
@@ -442,7 +456,7 @@ static int fsck_gitmodules_fn(const char *var, const char *value, void *vdata)
453c5b
 					      "disallowed submodule name: %s",
453c5b
 					      name);
453c5b
 	if (!strcmp(key, "url") && value &&
453c5b
-	    looks_like_command_line_option(value))
453c5b
+	    check_submodule_url(value) < 0)
453c5b
 		data->ret += data->error_func(data->obj, FSCK_ERROR,
453c5b
 					      "disallowed submodule url: %s",
453c5b
 					      value);
453c5b
diff --git a/t/t7416-submodule-dash-url.sh b/t/t7416-submodule-dash-url.sh
453c5b
index e85f2e9d29..5356d8766a 100755
453c5b
--- a/t/t7416-submodule-dash-url.sh
453c5b
+++ b/t/t7416-submodule-dash-url.sh
453c5b
@@ -1,6 +1,6 @@
453c5b
 #!/bin/sh
453c5b
 
453c5b
-test_description='check handling of .gitmodule url with dash'
453c5b
+test_description='check handling of disallowed .gitmodule urls'
453c5b
 . ./test-lib.sh
453c5b
 
453c5b
 test_expect_success 'create submodule with protected dash in url' '
453c5b
@@ -46,4 +46,20 @@ test_expect_success 'fsck rejects unprotected dash' '
453c5b
 	test_i18ngrep "disallowed submodule url" err
453c5b
 '
453c5b
 
453c5b
+test_expect_success 'fsck rejects embedded newline in url' '
453c5b
+	# create an orphan branch to avoid existing .gitmodules objects
453c5b
+	git checkout --orphan newline &&
453c5b
+	cat >.gitmodules <<-\EOF &&
453c5b
+	[submodule "foo"]
453c5b
+	url = "https://one.example.com?%0ahost=two.example.com/foo.git"
453c5b
+	EOF
453c5b
+	git add .gitmodules &&
453c5b
+	git commit -m "gitmodules with newline" &&
453c5b
+	test_when_finished "rm -rf dst" &&
453c5b
+	git init --bare dst &&
453c5b
+	( cd dst && git config transfer.fsckObjects true ) &&
453c5b
+	test_must_fail git push dst HEAD 2>err &&
453c5b
+	grep "disallowed submodule url" err
453c5b
+'
453c5b
+
453c5b
 test_done
453c5b
-- 
453c5b
2.26.2
453c5b
453c5b
453c5b
From a44bd3335d9dd715e2d141a593704aababcc83e4 Mon Sep 17 00:00:00 2001
453c5b
From: Jeff King <peff@peff.net>
453c5b
Date: Sat, 18 Apr 2020 20:47:30 -0700
453c5b
Subject: [PATCH 04/11] t0300: use more realistic inputs
453c5b
453c5b
Many of the tests in t0300 give partial inputs to git-credential,
453c5b
omitting a protocol or hostname. We're checking only high-level things
453c5b
like whether and how helpers are invoked at all, and we don't care about
453c5b
specific hosts. However, in preparation for tightening up the rules
453c5b
about when we're willing to run a helper, let's start using input that's
453c5b
a bit more realistic: pretend as if http://example.com is being
453c5b
examined.
453c5b
453c5b
This shouldn't change the point of any of the tests, but do note we have
453c5b
to adjust the expected output to accommodate this (filling a credential
453c5b
will repeat back the protocol/host fields to stdout, and the helper
453c5b
debug messages and askpass prompt will change on stderr).
453c5b
453c5b
Signed-off-by: Jeff King <peff@peff.net>
453c5b
Reviewed-by: Taylor Blau <me@ttaylorr.com>
453c5b
Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
453c5b
---
453c5b
 t/t0300-credentials.sh | 76 ++++++++++++++++++++++++++++++++++++++++--
453c5b
 1 file changed, 73 insertions(+), 3 deletions(-)
453c5b
453c5b
diff --git a/t/t0300-credentials.sh b/t/t0300-credentials.sh
453c5b
index 88bcf6534b..2f0ff6bcae 100755
453c5b
--- a/t/t0300-credentials.sh
453c5b
+++ b/t/t0300-credentials.sh
453c5b
@@ -35,43 +35,71 @@ test_expect_success 'setup helper scripts' '
453c5b
 
453c5b
 test_expect_success 'credential_fill invokes helper' '
453c5b
 	check fill "verbatim foo bar" <<-\EOF
453c5b
+	protocol=http
453c5b
+	host=example.com
453c5b
 	--
453c5b
+	protocol=http
453c5b
+	host=example.com
453c5b
 	username=foo
453c5b
 	password=bar
453c5b
 	--
453c5b
 	verbatim: get
453c5b
+	verbatim: protocol=http
453c5b
+	verbatim: host=example.com
453c5b
 	EOF
453c5b
 '
453c5b
 
453c5b
 test_expect_success 'credential_fill invokes multiple helpers' '
453c5b
 	check fill useless "verbatim foo bar" <<-\EOF
453c5b
+	protocol=http
453c5b
+	host=example.com
453c5b
 	--
453c5b
+	protocol=http
453c5b
+	host=example.com
453c5b
 	username=foo
453c5b
 	password=bar
453c5b
 	--
453c5b
 	useless: get
453c5b
+	useless: protocol=http
453c5b
+	useless: host=example.com
453c5b
 	verbatim: get
453c5b
+	verbatim: protocol=http
453c5b
+	verbatim: host=example.com
453c5b
 	EOF
453c5b
 '
453c5b
 
453c5b
 test_expect_success 'credential_fill stops when we get a full response' '
453c5b
 	check fill "verbatim one two" "verbatim three four" <<-\EOF
453c5b
+	protocol=http
453c5b
+	host=example.com
453c5b
 	--
453c5b
+	protocol=http
453c5b
+	host=example.com
453c5b
 	username=one
453c5b
 	password=two
453c5b
 	--
453c5b
 	verbatim: get
453c5b
+	verbatim: protocol=http
453c5b
+	verbatim: host=example.com
453c5b
 	EOF
453c5b
 '
453c5b
 
453c5b
 test_expect_success 'credential_fill continues through partial response' '
453c5b
 	check fill "verbatim one \"\"" "verbatim two three" <<-\EOF
453c5b
+	protocol=http
453c5b
+	host=example.com
453c5b
 	--
453c5b
+	protocol=http
453c5b
+	host=example.com
453c5b
 	username=two
453c5b
 	password=three
453c5b
 	--
453c5b
 	verbatim: get
453c5b
+	verbatim: protocol=http
453c5b
+	verbatim: host=example.com
453c5b
 	verbatim: get
453c5b
+	verbatim: protocol=http
453c5b
+	verbatim: host=example.com
453c5b
 	verbatim: username=one
453c5b
 	EOF
453c5b
 '
453c5b
@@ -97,14 +125,20 @@ test_expect_success 'credential_fill passes along metadata' '
453c5b
 
453c5b
 test_expect_success 'credential_approve calls all helpers' '
453c5b
 	check approve useless "verbatim one two" <<-\EOF
453c5b
+	protocol=http
453c5b
+	host=example.com
453c5b
 	username=foo
453c5b
 	password=bar
453c5b
 	--
453c5b
 	--
453c5b
 	useless: store
453c5b
+	useless: protocol=http
453c5b
+	useless: host=example.com
453c5b
 	useless: username=foo
453c5b
 	useless: password=bar
453c5b
 	verbatim: store
453c5b
+	verbatim: protocol=http
453c5b
+	verbatim: host=example.com
453c5b
 	verbatim: username=foo
453c5b
 	verbatim: password=bar
453c5b
 	EOF
453c5b
@@ -112,6 +146,8 @@ test_expect_success 'credential_approve calls all helpers' '
453c5b
 
453c5b
 test_expect_success 'do not bother storing password-less credential' '
453c5b
 	check approve useless <<-\EOF
453c5b
+	protocol=http
453c5b
+	host=example.com
453c5b
 	username=foo
453c5b
 	--
453c5b
 	--
453c5b
@@ -121,14 +157,20 @@ test_expect_success 'do not bother storing password-less credential' '
453c5b
 
453c5b
 test_expect_success 'credential_reject calls all helpers' '
453c5b
 	check reject useless "verbatim one two" <<-\EOF
453c5b
+	protocol=http
453c5b
+	host=example.com
453c5b
 	username=foo
453c5b
 	password=bar
453c5b
 	--
453c5b
 	--
453c5b
 	useless: erase
453c5b
+	useless: protocol=http
453c5b
+	useless: host=example.com
453c5b
 	useless: username=foo
453c5b
 	useless: password=bar
453c5b
 	verbatim: erase
453c5b
+	verbatim: protocol=http
453c5b
+	verbatim: host=example.com
453c5b
 	verbatim: username=foo
453c5b
 	verbatim: password=bar
453c5b
 	EOF
453c5b
@@ -136,33 +178,49 @@ test_expect_success 'credential_reject calls all helpers' '
453c5b
 
453c5b
 test_expect_success 'usernames can be preserved' '
453c5b
 	check fill "verbatim \"\" three" <<-\EOF
453c5b
+	protocol=http
453c5b
+	host=example.com
453c5b
 	username=one
453c5b
 	--
453c5b
+	protocol=http
453c5b
+	host=example.com
453c5b
 	username=one
453c5b
 	password=three
453c5b
 	--
453c5b
 	verbatim: get
453c5b
+	verbatim: protocol=http
453c5b
+	verbatim: host=example.com
453c5b
 	verbatim: username=one
453c5b
 	EOF
453c5b
 '
453c5b
 
453c5b
 test_expect_success 'usernames can be overridden' '
453c5b
 	check fill "verbatim two three" <<-\EOF
453c5b
+	protocol=http
453c5b
+	host=example.com
453c5b
 	username=one
453c5b
 	--
453c5b
+	protocol=http
453c5b
+	host=example.com
453c5b
 	username=two
453c5b
 	password=three
453c5b
 	--
453c5b
 	verbatim: get
453c5b
+	verbatim: protocol=http
453c5b
+	verbatim: host=example.com
453c5b
 	verbatim: username=one
453c5b
 	EOF
453c5b
 '
453c5b
 
453c5b
 test_expect_success 'do not bother completing already-full credential' '
453c5b
 	check fill "verbatim three four" <<-\EOF
453c5b
+	protocol=http
453c5b
+	host=example.com
453c5b
 	username=one
453c5b
 	password=two
453c5b
 	--
453c5b
+	protocol=http
453c5b
+	host=example.com
453c5b
 	username=one
453c5b
 	password=two
453c5b
 	--
453c5b
@@ -174,23 +232,31 @@ test_expect_success 'do not bother completing already-full credential' '
453c5b
 # askpass helper is run, we know the internal getpass is working.
453c5b
 test_expect_success 'empty helper list falls back to internal getpass' '
453c5b
 	check fill <<-\EOF
453c5b
+	protocol=http
453c5b
+	host=example.com
453c5b
 	--
453c5b
+	protocol=http
453c5b
+	host=example.com
453c5b
 	username=askpass-username
453c5b
 	password=askpass-password
453c5b
 	--
453c5b
-	askpass: Username:
453c5b
-	askpass: Password:
453c5b
+	askpass: Username for '\''http://example.com'\'':
453c5b
+	askpass: Password for '\''http://askpass-username@example.com'\'':
453c5b
 	EOF
453c5b
 '
453c5b
 
453c5b
 test_expect_success 'internal getpass does not ask for known username' '
453c5b
 	check fill <<-\EOF
453c5b
+	protocol=http
453c5b
+	host=example.com
453c5b
 	username=foo
453c5b
 	--
453c5b
+	protocol=http
453c5b
+	host=example.com
453c5b
 	username=foo
453c5b
 	password=askpass-password
453c5b
 	--
453c5b
-	askpass: Password:
453c5b
+	askpass: Password for '\''http://foo@example.com'\'':
453c5b
 	EOF
453c5b
 '
453c5b
 
453c5b
@@ -202,7 +268,11 @@ HELPER="!f() {
453c5b
 test_expect_success 'respect configured credentials' '
453c5b
 	test_config credential.helper "$HELPER" &&
453c5b
 	check fill <<-\EOF
453c5b
+	protocol=http
453c5b
+	host=example.com
453c5b
 	--
453c5b
+	protocol=http
453c5b
+	host=example.com
453c5b
 	username=foo
453c5b
 	password=bar
453c5b
 	--
453c5b
-- 
453c5b
2.26.2
453c5b
453c5b
453c5b
From 6eae9cd97a8f481c04dd4f8ad507eeff69a335da Mon Sep 17 00:00:00 2001
453c5b
From: Jeff King <peff@peff.net>
453c5b
Date: Sat, 18 Apr 2020 20:48:05 -0700
453c5b
Subject: [PATCH 05/11] credential: parse URL without host as empty host, not
453c5b
 unset
453c5b
453c5b
We may feed a URL like "cert:///path/to/cert.pem" into the credential
453c5b
machinery to get the key for a client-side certificate. That
453c5b
credential has no hostname field, which is about to be disallowed (to
453c5b
avoid confusion with protocols where a helper _would_ expect a
453c5b
hostname).
453c5b
453c5b
This means as of the next patch, credential helpers won't work for
453c5b
unlocking certs. Let's fix that by doing two things:
453c5b
453c5b
  - when we parse a url with an empty host, set the host field to the
453c5b
    empty string (asking only to match stored entries with an empty
453c5b
    host) rather than NULL (asking to match _any_ host).
453c5b
453c5b
  - when we build a cert:// credential by hand, similarly assign an
453c5b
    empty string
453c5b
453c5b
It's the latter that is more likely to impact real users in practice,
453c5b
since it's what's used for http connections. But we don't have good
453c5b
infrastructure to test it.
453c5b
453c5b
The url-parsing version will help anybody using git-credential in a
453c5b
script, and is easy to test.
453c5b
453c5b
Signed-off-by: Jeff King <peff@peff.net>
453c5b
Reviewed-by: Taylor Blau <me@ttaylorr.com>
453c5b
Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
453c5b
---
453c5b
 credential.c           |  3 +--
453c5b
 http.c                 |  1 +
453c5b
 t/t0300-credentials.sh | 17 +++++++++++++++++
453c5b
 3 files changed, 19 insertions(+), 2 deletions(-)
453c5b
453c5b
diff --git a/credential.c b/credential.c
453c5b
index f56a0a2c02..745b0e6a6d 100644
453c5b
--- a/credential.c
453c5b
+++ b/credential.c
453c5b
@@ -369,8 +369,7 @@ int credential_from_url_gently(struct credential *c, const char *url,
453c5b
 
453c5b
 	if (proto_end - url > 0)
453c5b
 		c->protocol = xmemdupz(url, proto_end - url);
453c5b
-	if (slash - host > 0)
453c5b
-		c->host = url_decode_mem(host, slash - host);
453c5b
+	c->host = url_decode_mem(host, slash - host);
453c5b
 	/* Trim leading and trailing slashes from path */
453c5b
 	while (*slash == '/')
453c5b
 		slash++;
453c5b
diff --git a/http.c b/http.c
453c5b
index 3320590e4b..1533c9338c 100644
453c5b
--- a/http.c
453c5b
+++ b/http.c
453c5b
@@ -269,6 +269,7 @@ static int has_cert_password(void)
453c5b
 		return 0;
453c5b
 	if (!cert_auth.password) {
453c5b
 		cert_auth.protocol = xstrdup("cert");
453c5b
+		cert_auth.host = xstrdup("");
453c5b
 		cert_auth.username = xstrdup("");
453c5b
 		cert_auth.path = xstrdup(ssl_cert);
453c5b
 		credential_fill(&cert_auth);
453c5b
diff --git a/t/t0300-credentials.sh b/t/t0300-credentials.sh
453c5b
index 2f0ff6bcae..0bce92af98 100755
453c5b
--- a/t/t0300-credentials.sh
453c5b
+++ b/t/t0300-credentials.sh
453c5b
@@ -373,4 +373,21 @@ test_expect_success 'url parser ignores embedded newlines' '
453c5b
 	EOF
453c5b
 '
453c5b
 
453c5b
+test_expect_success 'host-less URLs are parsed as empty host' '
453c5b
+	check fill "verbatim foo bar" <<-\EOF
453c5b
+	url=cert:///path/to/cert.pem
453c5b
+	--
453c5b
+	protocol=cert
453c5b
+	host=
453c5b
+	path=path/to/cert.pem
453c5b
+	username=foo
453c5b
+	password=bar
453c5b
+	--
453c5b
+	verbatim: get
453c5b
+	verbatim: protocol=cert
453c5b
+	verbatim: host=
453c5b
+	verbatim: path=path/to/cert.pem
453c5b
+	EOF
453c5b
+'
453c5b
+
453c5b
 test_done
453c5b
-- 
453c5b
2.26.2
453c5b
453c5b
453c5b
From 215df2b679d0f0d3458fafe27f4e3a3f05544400 Mon Sep 17 00:00:00 2001
453c5b
From: Jeff King <peff@peff.net>
453c5b
Date: Sat, 18 Apr 2020 20:50:48 -0700
453c5b
Subject: [PATCH 06/11] credential: refuse to operate when missing host or
453c5b
 protocol
453c5b
453c5b
The credential helper protocol was designed to be very flexible: the
453c5b
fields it takes as input are treated as a pattern, and any missing
453c5b
fields are taken as wildcards. This allows unusual things like:
453c5b
453c5b
  echo protocol=https | git credential reject
453c5b
453c5b
to delete all stored https credentials (assuming the helpers themselves
453c5b
treat the input that way). But when helpers are invoked automatically by
453c5b
Git, this flexibility works against us. If for whatever reason we don't
453c5b
have a "host" field, then we'd match _any_ host. When you're filling a
453c5b
credential to send to a remote server, this is almost certainly not what
453c5b
you want.
453c5b
453c5b
Prevent this at the layer that writes to the credential helper. Add a
453c5b
check to the credential API that the host and protocol are always passed
453c5b
in, and add an assertion to the credential_write function that speaks
453c5b
credential helper protocol to be doubly sure.
453c5b
453c5b
There are a few ways this can be triggered in practice:
453c5b
453c5b
  - the "git credential" command passes along arbitrary credential
453c5b
    parameters it reads from stdin.
453c5b
453c5b
  - until the previous patch, when the host field of a URL is empty, we
453c5b
    would leave it unset (rather than setting it to the empty string)
453c5b
453c5b
  - a URL like "example.com/foo.git" is treated by curl as if "http://"
453c5b
    was present, but our parser sees it as a non-URL and leaves all
453c5b
    fields unset
453c5b
453c5b
  - the recent fix for URLs with embedded newlines blanks the URL but
453c5b
    otherwise continues. Rather than having the desired effect of
453c5b
    looking up no credential at all, many helpers will return _any_
453c5b
    credential
453c5b
453c5b
Our earlier test for an embedded newline didn't catch this because it
453c5b
only checked that the credential was cleared, but didn't configure an
453c5b
actual helper. Configuring the "verbatim" helper in the test would show
453c5b
that it is invoked (it's obviously a silly helper which doesn't look at
453c5b
its input, but the point is that it shouldn't be run at all). Since
453c5b
we're switching this case to die(), we don't need to bother with a
453c5b
helper. We can see the new behavior just by checking that the operation
453c5b
fails.
453c5b
453c5b
We'll add new tests covering partial input as well (these can be
453c5b
triggered through various means with url-parsing, but it's simpler to
453c5b
just check them directly, as we know we are covered even if the url
453c5b
parser changes behavior in the future).
453c5b
453c5b
[jn: changed to die() instead of logging and showing a manual
453c5b
 username/password prompt]
453c5b
453c5b
Reported-by: Carlo Arenas <carenas@gmail.com>
453c5b
Signed-off-by: Jeff King <peff@peff.net>
453c5b
Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
453c5b
---
453c5b
 credential.c           | 20 ++++++++++++++------
453c5b
 t/t0300-credentials.sh | 34 ++++++++++++++++++++++++++--------
453c5b
 2 files changed, 40 insertions(+), 14 deletions(-)
453c5b
453c5b
diff --git a/credential.c b/credential.c
453c5b
index 745b0e6a6d..930f8b9fe9 100644
453c5b
--- a/credential.c
453c5b
+++ b/credential.c
453c5b
@@ -85,6 +85,11 @@ static int proto_is_http(const char *s)
453c5b
 
453c5b
 static void credential_apply_config(struct credential *c)
453c5b
 {
453c5b
+	if (!c->host)
453c5b
+		die(_("refusing to work with credential missing host field"));
453c5b
+	if (!c->protocol)
453c5b
+		die(_("refusing to work with credential missing protocol field"));
453c5b
+
453c5b
 	if (c->configured)
453c5b
 		return;
453c5b
 	git_config(credential_config_callback, c);
453c5b
@@ -186,8 +191,11 @@ int credential_read(struct credential *c, FILE *fp)
453c5b
 	return 0;
453c5b
 }
453c5b
 
453c5b
-static void credential_write_item(FILE *fp, const char *key, const char *value)
453c5b
+static void credential_write_item(FILE *fp, const char *key, const char *value,
453c5b
+				  int required)
453c5b
 {
453c5b
+	if (!value && required)
453c5b
+		die("BUG: credential value for %s is missing", key);
453c5b
 	if (!value)
453c5b
 		return;
453c5b
 	if (strchr(value, '\n'))
453c5b
@@ -197,11 +205,11 @@ static void credential_write_item(FILE *fp, const char *key, const char *value)
453c5b
 
453c5b
 void credential_write(const struct credential *c, FILE *fp)
453c5b
 {
453c5b
-	credential_write_item(fp, "protocol", c->protocol);
453c5b
-	credential_write_item(fp, "host", c->host);
453c5b
-	credential_write_item(fp, "path", c->path);
453c5b
-	credential_write_item(fp, "username", c->username);
453c5b
-	credential_write_item(fp, "password", c->password);
453c5b
+	credential_write_item(fp, "protocol", c->protocol, 1);
453c5b
+	credential_write_item(fp, "host", c->host, 1);
453c5b
+	credential_write_item(fp, "path", c->path, 0);
453c5b
+	credential_write_item(fp, "username", c->username, 0);
453c5b
+	credential_write_item(fp, "password", c->password, 0);
453c5b
 }
453c5b
 
453c5b
 static int run_credential_helper(struct credential *c,
453c5b
diff --git a/t/t0300-credentials.sh b/t/t0300-credentials.sh
453c5b
index 0bce92af98..fa903bc9ba 100755
453c5b
--- a/t/t0300-credentials.sh
453c5b
+++ b/t/t0300-credentials.sh
453c5b
@@ -359,18 +359,16 @@ test_expect_success 'http paths can be part of context' '
453c5b
 	EOF
453c5b
 '
453c5b
 
453c5b
-test_expect_success 'url parser ignores embedded newlines' '
453c5b
-	check fill <<-EOF
453c5b
+test_expect_success 'url parser rejects embedded newlines' '
453c5b
+	test_must_fail git credential fill 2>stderr <<-\EOF &&
453c5b
 	url=https://one.example.com?%0ahost=two.example.com/
453c5b
-	--
453c5b
-	username=askpass-username
453c5b
-	password=askpass-password
453c5b
-	--
453c5b
+	EOF
453c5b
+	cat >expect <<-\EOF &&
453c5b
 	warning: url contains a newline in its host component: https://one.example.com?%0ahost=two.example.com/
453c5b
 	warning: skipping credential lookup for url: https://one.example.com?%0ahost=two.example.com/
453c5b
-	askpass: Username:
453c5b
-	askpass: Password:
453c5b
+	fatal: refusing to work with credential missing host field
453c5b
 	EOF
453c5b
+	test_i18ncmp expect stderr
453c5b
 '
453c5b
 
453c5b
 test_expect_success 'host-less URLs are parsed as empty host' '
453c5b
@@ -390,4 +388,24 @@ test_expect_success 'host-less URLs are parsed as empty host' '
453c5b
 	EOF
453c5b
 '
453c5b
 
453c5b
+test_expect_success 'credential system refuses to work with missing host' '
453c5b
+	test_must_fail git credential fill 2>stderr <<-\EOF &&
453c5b
+	protocol=http
453c5b
+	EOF
453c5b
+	cat >expect <<-\EOF &&
453c5b
+	fatal: refusing to work with credential missing host field
453c5b
+	EOF
453c5b
+	test_i18ncmp expect stderr
453c5b
+'
453c5b
+
453c5b
+test_expect_success 'credential system refuses to work with missing protocol' '
453c5b
+	test_must_fail git credential fill 2>stderr <<-\EOF &&
453c5b
+	host=example.com
453c5b
+	EOF
453c5b
+	cat >expect <<-\EOF &&
453c5b
+	fatal: refusing to work with credential missing protocol field
453c5b
+	EOF
453c5b
+	test_i18ncmp expect stderr
453c5b
+'
453c5b
+
453c5b
 test_done
453c5b
-- 
453c5b
2.26.2
453c5b
453c5b
453c5b
From 38cf97b5bb0fe73ae21f9a840caeb26c600cb624 Mon Sep 17 00:00:00 2001
453c5b
From: Jonathan Nieder <jrnieder@gmail.com>
453c5b
Date: Sat, 18 Apr 2020 20:52:34 -0700
453c5b
Subject: [PATCH 07/11] fsck: convert gitmodules url to URL passed to curl
453c5b
453c5b
In 07259e74ec1 (fsck: detect gitmodules URLs with embedded newlines,
453c5b
2020-03-11), git fsck learned to check whether URLs in .gitmodules could
453c5b
be understood by the credential machinery when they are handled by
453c5b
git-remote-curl.
453c5b
453c5b
However, the check is overbroad: it checks all URLs instead of only
453c5b
URLs that would be passed to git-remote-curl. In principle a git:// or
453c5b
file:/// URL does not need to follow the same conventions as an http://
453c5b
URL; in particular, git:// and file:// protocols are not succeptible to
453c5b
issues in the credential API because they do not support attaching
453c5b
credentials.
453c5b
453c5b
In the HTTP case, the URL in .gitmodules does not always match the URL
453c5b
that would be passed to git-remote-curl and the credential machinery:
453c5b
Git's URL syntax allows specifying a remote helper followed by a "::"
453c5b
delimiter and a URL to be passed to it, so that
453c5b
453c5b
	git ls-remote http::https://example.com/repo.git
453c5b
453c5b
invokes git-remote-http with https://example.com/repo.git as its URL
453c5b
argument. With today's checks, that distinction does not make a
453c5b
difference, but for a check we are about to introduce (for empty URL
453c5b
schemes) it will matter.
453c5b
453c5b
.gitmodules files also support relative URLs. To ensure coverage for the
453c5b
https based embedded-newline attack, urldecode and check them directly
453c5b
for embedded newlines.
453c5b
453c5b
Helped-by: Jeff King <peff@peff.net>
453c5b
Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
453c5b
Reviewed-by: Jeff King <peff@peff.net>
453c5b
---
453c5b
 fsck.c                        | 94 +++++++++++++++++++++++++++++++++--
453c5b
 t/t7416-submodule-dash-url.sh | 29 +++++++++++
453c5b
 2 files changed, 118 insertions(+), 5 deletions(-)
453c5b
453c5b
diff --git a/fsck.c b/fsck.c
453c5b
index b72faea0c2..a265568eda 100644
453c5b
--- a/fsck.c
453c5b
+++ b/fsck.c
453c5b
@@ -8,6 +8,7 @@
453c5b
 #include "fsck.h"
453c5b
 #include "hashmap.h"
453c5b
 #include "submodule.h"
453c5b
+#include "url.h"
453c5b
 #include "credential.h"
453c5b
 
453c5b
 struct oidhash_entry {
453c5b
@@ -420,17 +421,100 @@ static int fsck_tag(struct tag *tag, const char *data,
453c5b
 	return 0;
453c5b
 }
453c5b
 
453c5b
+/*
453c5b
+ * Like builtin/submodule--helper.c's starts_with_dot_slash, but without
453c5b
+ * relying on the platform-dependent is_dir_sep helper.
453c5b
+ *
453c5b
+ * This is for use in checking whether a submodule URL is interpreted as
453c5b
+ * relative to the current directory on any platform, since \ is a
453c5b
+ * directory separator on Windows but not on other platforms.
453c5b
+ */
453c5b
+static int starts_with_dot_slash(const char *str)
453c5b
+{
453c5b
+	return str[0] == '.' && (str[1] == '/' || str[1] == '\\');
453c5b
+}
453c5b
+
453c5b
+/*
453c5b
+ * Like starts_with_dot_slash, this is a variant of submodule--helper's
453c5b
+ * helper of the same name with the twist that it accepts backslash as a
453c5b
+ * directory separator even on non-Windows platforms.
453c5b
+ */
453c5b
+static int starts_with_dot_dot_slash(const char *str)
453c5b
+{
453c5b
+	return str[0] == '.' && starts_with_dot_slash(str + 1);
453c5b
+}
453c5b
+
453c5b
+static int submodule_url_is_relative(const char *url)
453c5b
+{
453c5b
+	return starts_with_dot_slash(url) || starts_with_dot_dot_slash(url);
453c5b
+}
453c5b
+
453c5b
+/*
453c5b
+ * Check whether a transport is implemented by git-remote-curl.
453c5b
+ *
453c5b
+ * If it is, returns 1 and writes the URL that would be passed to
453c5b
+ * git-remote-curl to the "out" parameter.
453c5b
+ *
453c5b
+ * Otherwise, returns 0 and leaves "out" untouched.
453c5b
+ *
453c5b
+ * Examples:
453c5b
+ *   http::https://example.com/repo.git -> 1, https://example.com/repo.git
453c5b
+ *   https://example.com/repo.git -> 1, https://example.com/repo.git
453c5b
+ *   git://example.com/repo.git -> 0
453c5b
+ *
453c5b
+ * This is for use in checking for previously exploitable bugs that
453c5b
+ * required a submodule URL to be passed to git-remote-curl.
453c5b
+ */
453c5b
+static int url_to_curl_url(const char *url, const char **out)
453c5b
+{
453c5b
+	/*
453c5b
+	 * We don't need to check for case-aliases, "http.exe", and so
453c5b
+	 * on because in the default configuration, is_transport_allowed
453c5b
+	 * prevents URLs with those schemes from being cloned
453c5b
+	 * automatically.
453c5b
+	 */
453c5b
+	if ((*out = skip_prefix(url, "http::")) ||
453c5b
+	    (*out = skip_prefix(url, "https::")) ||
453c5b
+	    (*out = skip_prefix(url, "ftp::")) ||
453c5b
+	    (*out = skip_prefix(url, "ftps::")))
453c5b
+		return 1;
453c5b
+	if (starts_with(url, "http://") ||
453c5b
+	    starts_with(url, "https://") ||
453c5b
+	    starts_with(url, "ftp://") ||
453c5b
+	    starts_with(url, "ftps://")) {
453c5b
+		*out = url;
453c5b
+		return 1;
453c5b
+	}
453c5b
+	return 0;
453c5b
+}
453c5b
+
453c5b
 static int check_submodule_url(const char *url)
453c5b
 {
453c5b
-	struct credential c = CREDENTIAL_INIT;
453c5b
-	int ret;
453c5b
+	const char *curl_url;
453c5b
 
453c5b
 	if (looks_like_command_line_option(url))
453c5b
 		return -1;
453c5b
 
453c5b
-	ret = credential_from_url_gently(&c, url, 1);
453c5b
-	credential_clear(&c);
453c5b
-	return ret;
453c5b
+	if (submodule_url_is_relative(url)) {
453c5b
+		/*
453c5b
+		 * This could be appended to an http URL and url-decoded;
453c5b
+		 * check for malicious characters.
453c5b
+		 */
453c5b
+		char *decoded = url_decode(url);
453c5b
+		int has_nl = !!strchr(decoded, '\n');
453c5b
+		free(decoded);
453c5b
+		if (has_nl)
453c5b
+			return -1;
453c5b
+	}
453c5b
+
453c5b
+	else if (url_to_curl_url(url, &curl_url)) {
453c5b
+		struct credential c = CREDENTIAL_INIT;
453c5b
+		int ret = credential_from_url_gently(&c, curl_url, 1);
453c5b
+		credential_clear(&c);
453c5b
+		return ret;
453c5b
+	}
453c5b
+
453c5b
+	return 0;
453c5b
 }
453c5b
 
453c5b
 struct fsck_gitmodules_data {
453c5b
diff --git a/t/t7416-submodule-dash-url.sh b/t/t7416-submodule-dash-url.sh
453c5b
index 5356d8766a..41bec56953 100755
453c5b
--- a/t/t7416-submodule-dash-url.sh
453c5b
+++ b/t/t7416-submodule-dash-url.sh
453c5b
@@ -46,6 +46,20 @@ test_expect_success 'fsck rejects unprotected dash' '
453c5b
 	test_i18ngrep "disallowed submodule url" err
453c5b
 '
453c5b
 
453c5b
+test_expect_success 'fsck permits embedded newline with unrecognized scheme' '
453c5b
+	git checkout --orphan newscheme &&
453c5b
+	cat >.gitmodules <<-\EOF &&
453c5b
+	[submodule "foo"]
453c5b
+		url = "data://acjbkd%0akajfdickajkd"
453c5b
+	EOF
453c5b
+	git add .gitmodules &&
453c5b
+	git commit -m "gitmodules with unrecognized scheme" &&
453c5b
+	test_when_finished "rm -rf dst" &&
453c5b
+	git init --bare dst &&
453c5b
+	( cd dst && git config transfer.fsckObjects true ) &&
453c5b
+	git push dst HEAD
453c5b
+'
453c5b
+
453c5b
 test_expect_success 'fsck rejects embedded newline in url' '
453c5b
 	# create an orphan branch to avoid existing .gitmodules objects
453c5b
 	git checkout --orphan newline &&
453c5b
@@ -62,4 +76,19 @@ test_expect_success 'fsck rejects embedded newline in url' '
453c5b
 	grep "disallowed submodule url" err
453c5b
 '
453c5b
 
453c5b
+test_expect_success 'fsck rejects embedded newline in relative url' '
453c5b
+	git checkout --orphan relative-newline &&
453c5b
+	cat >.gitmodules <<-\EOF &&
453c5b
+	[submodule "foo"]
453c5b
+		url = "./%0ahost=two.example.com/foo.git"
453c5b
+	EOF
453c5b
+	git add .gitmodules &&
453c5b
+	git commit -m "relative url with newline" &&
453c5b
+	test_when_finished "rm -rf dst" &&
453c5b
+	git init --bare dst &&
453c5b
+	( cd dst && git config transfer.fsckObjects true ) &&
453c5b
+	test_must_fail git push dst HEAD 2>err &&
453c5b
+	grep "disallowed submodule url" err
453c5b
+'
453c5b
+
453c5b
 test_done
453c5b
-- 
453c5b
2.26.2
453c5b
453c5b
453c5b
From d98d29c00f88d6fe399dd11fd77b88074bd49924 Mon Sep 17 00:00:00 2001
453c5b
From: Jeff King <peff@peff.net>
453c5b
Date: Sat, 18 Apr 2020 20:53:09 -0700
453c5b
Subject: [PATCH 08/11] credential: die() when parsing invalid urls
453c5b
453c5b
When we try to initialize credential loading by URL and find that the
453c5b
URL is invalid, we set all fields to NULL in order to avoid acting on
453c5b
malicious input. Later when we request credentials, we diagonse the
453c5b
erroneous input:
453c5b
453c5b
	fatal: refusing to work with credential missing host field
453c5b
453c5b
This is problematic in two ways:
453c5b
453c5b
- The message doesn't tell the user *why* we are missing the host
453c5b
  field, so they can't tell from this message alone how to recover.
453c5b
  There can be intervening messages after the original warning of
453c5b
  bad input, so the user may not have the context to put two and two
453c5b
  together.
453c5b
453c5b
- The error only occurs when we actually need to get a credential.  If
453c5b
  the URL permits anonymous access, the only encouragement the user gets
453c5b
  to correct their bogus URL is a quiet warning.
453c5b
453c5b
  This is inconsistent with the check we perform in fsck, where any use
453c5b
  of such a URL as a submodule is an error.
453c5b
453c5b
When we see such a bogus URL, let's not try to be nice and continue
453c5b
without helpers. Instead, die() immediately. This is simpler and
453c5b
obviously safe. And there's very little chance of disrupting a normal
453c5b
workflow.
453c5b
453c5b
It's _possible_ that somebody has a legitimate URL with a raw newline in
453c5b
it. It already wouldn't work with credential helpers, so this patch
453c5b
steps that up from an inconvenience to "we will refuse to work with it
453c5b
at all". If such a case does exist, we should figure out a way to work
453c5b
with it (especially if the newline is only in the path component, which
453c5b
we normally don't even pass to helpers). But until we see a real report,
453c5b
we're better off being defensive.
453c5b
453c5b
Reported-by: Carlo Arenas <carenas@gmail.com>
453c5b
Signed-off-by: Jeff King <peff@peff.net>
453c5b
Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
453c5b
---
453c5b
 credential.c           | 6 ++----
453c5b
 t/t0300-credentials.sh | 3 +--
453c5b
 2 files changed, 3 insertions(+), 6 deletions(-)
453c5b
453c5b
diff --git a/credential.c b/credential.c
453c5b
index 930f8b9fe9..725e917b2d 100644
453c5b
--- a/credential.c
453c5b
+++ b/credential.c
453c5b
@@ -401,8 +401,6 @@ int credential_from_url_gently(struct credential *c, const char *url,
453c5b
 
453c5b
 void credential_from_url(struct credential *c, const char *url)
453c5b
 {
453c5b
-	if (credential_from_url_gently(c, url, 0) < 0) {
453c5b
-		warning(_("skipping credential lookup for url: %s"), url);
453c5b
-		credential_clear(c);
453c5b
-	}
453c5b
+	if (credential_from_url_gently(c, url, 0) < 0)
453c5b
+		die(_("credential url cannot be parsed: %s"), url);
453c5b
 }
453c5b
diff --git a/t/t0300-credentials.sh b/t/t0300-credentials.sh
453c5b
index fa903bc9ba..9892464eba 100755
453c5b
--- a/t/t0300-credentials.sh
453c5b
+++ b/t/t0300-credentials.sh
453c5b
@@ -365,8 +365,7 @@ test_expect_success 'url parser rejects embedded newlines' '
453c5b
 	EOF
453c5b
 	cat >expect <<-\EOF &&
453c5b
 	warning: url contains a newline in its host component: https://one.example.com?%0ahost=two.example.com/
453c5b
-	warning: skipping credential lookup for url: https://one.example.com?%0ahost=two.example.com/
453c5b
-	fatal: refusing to work with credential missing host field
453c5b
+	fatal: credential url cannot be parsed: https://one.example.com?%0ahost=two.example.com/
453c5b
 	EOF
453c5b
 	test_i18ncmp expect stderr
453c5b
 '
453c5b
-- 
453c5b
2.26.2
453c5b
453c5b
453c5b
From accb9edcd64007cf940be7b9e6a209ebcc650e8c Mon Sep 17 00:00:00 2001
453c5b
From: Jonathan Nieder <jrnieder@gmail.com>
453c5b
Date: Sat, 18 Apr 2020 20:54:13 -0700
453c5b
Subject: [PATCH 09/11] credential: treat URL without scheme as invalid
453c5b
453c5b
libcurl permits making requests without a URL scheme specified.  In
453c5b
this case, it guesses the URL from the hostname, so I can run
453c5b
453c5b
	git ls-remote http::ftp.example.com/path/to/repo
453c5b
453c5b
and it would make an FTP request.
453c5b
453c5b
Any user intentionally using such a URL is likely to have made a typo.
453c5b
Unfortunately, credential_from_url is not able to determine the host and
453c5b
protocol in order to determine appropriate credentials to send, and
453c5b
until "credential: refuse to operate when missing host or protocol",
453c5b
this resulted in another host's credentials being leaked to the named
453c5b
host.
453c5b
453c5b
Teach credential_from_url_gently to consider such a URL to be invalid
453c5b
so that fsck can detect and block gitmodules files with such URLs,
453c5b
allowing server operators to avoid serving them to downstream users
453c5b
running older versions of Git.
453c5b
453c5b
This also means that when such URLs are passed on the command line, Git
453c5b
will print a clearer error so affected users can switch to the simpler
453c5b
URL that explicitly specifies the host and protocol they intend.
453c5b
453c5b
One subtlety: .gitmodules files can contain relative URLs, representing
453c5b
a URL relative to the URL they were cloned from.  The relative URL
453c5b
resolver used for .gitmodules can follow ".." components out of the path
453c5b
part and past the host part of a URL, meaning that such a relative URL
453c5b
can be used to traverse from a https://foo.example.com/innocent
453c5b
superproject to a https::attacker.example.com/exploit submodule.
453c5b
Fortunately a leading ':' in the first path component after a series of
453c5b
leading './' and '../' components is unlikely to show up in other
453c5b
contexts, so we can catch this by detecting that pattern.
453c5b
453c5b
Reported-by: Jeff King <peff@peff.net>
453c5b
Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
453c5b
Reviewed-by: Jeff King <peff@peff.net>
453c5b
---
453c5b
 credential.c                  |  7 ++++--
453c5b
 fsck.c                        | 47 +++++++++++++++++++++++++++++++++--
453c5b
 t/t5550-http-fetch.sh         |  5 ++++
453c5b
 t/t7416-submodule-dash-url.sh | 32 ++++++++++++++++++++++++
453c5b
 4 files changed, 87 insertions(+), 4 deletions(-)
453c5b
453c5b
diff --git a/credential.c b/credential.c
453c5b
index 725e917b2d..f857f7d1e1 100644
453c5b
--- a/credential.c
453c5b
+++ b/credential.c
453c5b
@@ -353,8 +353,11 @@ int credential_from_url_gently(struct credential *c, const char *url,
453c5b
 	 *   (3) proto://<user>:<pass>@<host>/...
453c5b
 	 */
453c5b
 	proto_end = strstr(url, "://");
453c5b
-	if (!proto_end)
453c5b
-		return 0;
453c5b
+	if (!proto_end) {
453c5b
+		if (!quiet)
453c5b
+			warning(_("url has no scheme: %s"), url);
453c5b
+		return -1;
453c5b
+	}
453c5b
 	cp = proto_end + 3;
453c5b
 	at = strchr(cp, '@');
453c5b
 	colon = strchr(cp, ':');
453c5b
diff --git a/fsck.c b/fsck.c
453c5b
index a265568eda..90aea117c9 100644
453c5b
--- a/fsck.c
453c5b
+++ b/fsck.c
453c5b
@@ -449,6 +449,34 @@ static int submodule_url_is_relative(const char *url)
453c5b
 	return starts_with_dot_slash(url) || starts_with_dot_dot_slash(url);
453c5b
 }
453c5b
 
453c5b
+/*
453c5b
+ * Count directory components that a relative submodule URL should chop
453c5b
+ * from the remote_url it is to be resolved against.
453c5b
+ *
453c5b
+ * In other words, this counts "../" components at the start of a
453c5b
+ * submodule URL.
453c5b
+ *
453c5b
+ * Returns the number of directory components to chop and writes a
453c5b
+ * pointer to the next character of url after all leading "./" and
453c5b
+ * "../" components to out.
453c5b
+ */
453c5b
+static int count_leading_dotdots(const char *url, const char **out)
453c5b
+{
453c5b
+	int result = 0;
453c5b
+	while (1) {
453c5b
+		if (starts_with_dot_dot_slash(url)) {
453c5b
+			result++;
453c5b
+			url += strlen("../");
453c5b
+			continue;
453c5b
+		}
453c5b
+		if (starts_with_dot_slash(url)) {
453c5b
+			url += strlen("./");
453c5b
+			continue;
453c5b
+		}
453c5b
+		*out = url;
453c5b
+		return result;
453c5b
+	}
453c5b
+}
453c5b
 /*
453c5b
  * Check whether a transport is implemented by git-remote-curl.
453c5b
  *
453c5b
@@ -496,15 +524,30 @@ static int check_submodule_url(const char *url)
453c5b
 		return -1;
453c5b
 
453c5b
 	if (submodule_url_is_relative(url)) {
453c5b
+		char *decoded;
453c5b
+		const char *next;
453c5b
+		int has_nl;
453c5b
+
453c5b
 		/*
453c5b
 		 * This could be appended to an http URL and url-decoded;
453c5b
 		 * check for malicious characters.
453c5b
 		 */
453c5b
-		char *decoded = url_decode(url);
453c5b
-		int has_nl = !!strchr(decoded, '\n');
453c5b
+		decoded = url_decode(url);
453c5b
+		has_nl = !!strchr(decoded, '\n');
453c5b
+
453c5b
 		free(decoded);
453c5b
 		if (has_nl)
453c5b
 			return -1;
453c5b
+
453c5b
+		/*
453c5b
+		 * URLs which escape their root via "../" can overwrite
453c5b
+		 * the host field and previous components, resolving to
453c5b
+		 * URLs like https::example.com/submodule.git that were
453c5b
+		 * susceptible to CVE-2020-11008.
453c5b
+		 */
453c5b
+		if (count_leading_dotdots(url, &next) > 0 &&
453c5b
+		    *next == ':')
453c5b
+			return -1;
453c5b
 	}
453c5b
 
453c5b
 	else if (url_to_curl_url(url, &curl_url)) {
453c5b
diff --git a/t/t5550-http-fetch.sh b/t/t5550-http-fetch.sh
453c5b
index f7d0f146f0..29fdae9a68 100755
453c5b
--- a/t/t5550-http-fetch.sh
453c5b
+++ b/t/t5550-http-fetch.sh
453c5b
@@ -172,5 +172,10 @@ test_expect_success 'did not use upload-pack service' '
453c5b
 	test_cmp exp act
453c5b
 '
453c5b
 
453c5b
+test_expect_success 'remote-http complains cleanly about malformed urls' '
453c5b
+	test_must_fail git remote-http http::/example.com/repo.git 2>stderr &&
453c5b
+	test_i18ngrep "url has no scheme" stderr
453c5b
+'
453c5b
+
453c5b
 stop_httpd
453c5b
 test_done
453c5b
diff --git a/t/t7416-submodule-dash-url.sh b/t/t7416-submodule-dash-url.sh
453c5b
index 41bec56953..1dc8333eb3 100755
453c5b
--- a/t/t7416-submodule-dash-url.sh
453c5b
+++ b/t/t7416-submodule-dash-url.sh
453c5b
@@ -46,6 +46,38 @@ test_expect_success 'fsck rejects unprotected dash' '
453c5b
 	test_i18ngrep "disallowed submodule url" err
453c5b
 '
453c5b
 
453c5b
+test_expect_success 'fsck rejects missing URL scheme' '
453c5b
+	git checkout --orphan missing-scheme &&
453c5b
+	cat >.gitmodules <<-\EOF &&
453c5b
+	[submodule "foo"]
453c5b
+		url = http::one.example.com/foo.git
453c5b
+	EOF
453c5b
+	git add .gitmodules &&
453c5b
+	test_tick &&
453c5b
+	git commit -m "gitmodules with missing URL scheme" &&
453c5b
+	test_when_finished "rm -rf dst" &&
453c5b
+	git init --bare dst &&
453c5b
+	( cd dst && git config transfer.fsckObjects true ) &&
453c5b
+	test_must_fail git push dst HEAD 2>err &&
453c5b
+	grep "disallowed submodule url" err
453c5b
+'
453c5b
+
453c5b
+test_expect_success 'fsck rejects relative URL resolving to missing scheme' '
453c5b
+	git checkout --orphan relative-missing-scheme &&
453c5b
+	cat >.gitmodules <<-\EOF &&
453c5b
+	[submodule "foo"]
453c5b
+		url = "..\\../.\\../:one.example.com/foo.git"
453c5b
+	EOF
453c5b
+	git add .gitmodules &&
453c5b
+	test_tick &&
453c5b
+	git commit -m "gitmodules with relative URL that strips off scheme" &&
453c5b
+	test_when_finished "rm -rf dst" &&
453c5b
+	git init --bare dst &&
453c5b
+	( cd dst && git config transfer.fsckObjects true ) &&
453c5b
+	test_must_fail git push dst HEAD 2>err &&
453c5b
+	grep "disallowed submodule url" err
453c5b
+'
453c5b
+
453c5b
 test_expect_success 'fsck permits embedded newline with unrecognized scheme' '
453c5b
 	git checkout --orphan newscheme &&
453c5b
 	cat >.gitmodules <<-\EOF &&
453c5b
-- 
453c5b
2.26.2
453c5b
453c5b
453c5b
From 8bd36d7015bc9ffbd8ef58227ae5d9928fa708cb Mon Sep 17 00:00:00 2001
453c5b
From: Jonathan Nieder <jrnieder@gmail.com>
453c5b
Date: Sat, 18 Apr 2020 20:54:57 -0700
453c5b
Subject: [PATCH 10/11] credential: treat URL with empty scheme as invalid
453c5b
453c5b
Until "credential: refuse to operate when missing host or protocol",
453c5b
Git's credential handling code interpreted URLs with empty scheme to
453c5b
mean "give me credentials matching this host for any protocol".
453c5b
453c5b
Luckily libcurl does not recognize such URLs (it tries to look for a
453c5b
protocol named "" and fails). Just in case that changes, let's reject
453c5b
them within Git as well. This way, credential_from_url is guaranteed to
453c5b
always produce a "struct credential" with protocol and host set.
453c5b
453c5b
Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
453c5b
---
453c5b
 credential.c                  |  5 ++---
453c5b
 t/t5550-http-fetch.sh         |  9 +++++++++
453c5b
 t/t7416-submodule-dash-url.sh | 32 ++++++++++++++++++++++++++++++++
453c5b
 3 files changed, 43 insertions(+), 3 deletions(-)
453c5b
453c5b
diff --git a/credential.c b/credential.c
453c5b
index f857f7d1e1..323f9025f0 100644
453c5b
--- a/credential.c
453c5b
+++ b/credential.c
453c5b
@@ -353,7 +353,7 @@ int credential_from_url_gently(struct credential *c, const char *url,
453c5b
 	 *   (3) proto://<user>:<pass>@<host>/...
453c5b
 	 */
453c5b
 	proto_end = strstr(url, "://");
453c5b
-	if (!proto_end) {
453c5b
+	if (!proto_end || proto_end == url) {
453c5b
 		if (!quiet)
453c5b
 			warning(_("url has no scheme: %s"), url);
453c5b
 		return -1;
453c5b
@@ -378,8 +378,7 @@ int credential_from_url_gently(struct credential *c, const char *url,
453c5b
 		host = at + 1;
453c5b
 	}
453c5b
 
453c5b
-	if (proto_end - url > 0)
453c5b
-		c->protocol = xmemdupz(url, proto_end - url);
453c5b
+	c->protocol = xmemdupz(url, proto_end - url);
453c5b
 	c->host = url_decode_mem(host, slash - host);
453c5b
 	/* Trim leading and trailing slashes from path */
453c5b
 	while (*slash == '/')
453c5b
diff --git a/t/t5550-http-fetch.sh b/t/t5550-http-fetch.sh
453c5b
index 29fdae9a68..afeb90fa66 100755
453c5b
--- a/t/t5550-http-fetch.sh
453c5b
+++ b/t/t5550-http-fetch.sh
453c5b
@@ -177,5 +177,14 @@ test_expect_success 'remote-http complains cleanly about malformed urls' '
453c5b
 	test_i18ngrep "url has no scheme" stderr
453c5b
 '
453c5b
 
453c5b
+# NEEDSWORK: Writing commands to git-remote-curl can race against the latter
453c5b
+# erroring out, producing SIGPIPE. Remove "ok=sigpipe" once transport-helper has
453c5b
+# learned to handle early remote helper failures more cleanly.
453c5b
+test_expect_success 'remote-http complains cleanly about empty scheme' '
453c5b
+	test_must_fail ok=sigpipe git ls-remote \
453c5b
+		http::${HTTPD_URL#http}/dumb/repo.git 2>stderr &&
453c5b
+	test_i18ngrep "url has no scheme" stderr
453c5b
+'
453c5b
+
453c5b
 stop_httpd
453c5b
 test_done
453c5b
diff --git a/t/t7416-submodule-dash-url.sh b/t/t7416-submodule-dash-url.sh
453c5b
index 1dc8333eb3..9c7a8113fa 100755
453c5b
--- a/t/t7416-submodule-dash-url.sh
453c5b
+++ b/t/t7416-submodule-dash-url.sh
453c5b
@@ -78,6 +78,38 @@ test_expect_success 'fsck rejects relative URL resolving to missing scheme' '
453c5b
 	grep "disallowed submodule url" err
453c5b
 '
453c5b
 
453c5b
+test_expect_success 'fsck rejects empty URL scheme' '
453c5b
+	git checkout --orphan empty-scheme &&
453c5b
+	cat >.gitmodules <<-\EOF &&
453c5b
+	[submodule "foo"]
453c5b
+		url = http::://one.example.com/foo.git
453c5b
+	EOF
453c5b
+	git add .gitmodules &&
453c5b
+	test_tick &&
453c5b
+	git commit -m "gitmodules with empty URL scheme" &&
453c5b
+	test_when_finished "rm -rf dst" &&
453c5b
+	git init --bare dst &&
453c5b
+	( cd dst && git config transfer.fsckObjects true ) &&
453c5b
+	test_must_fail git push dst HEAD 2>err &&
453c5b
+	grep "disallowed submodule url" err
453c5b
+'
453c5b
+
453c5b
+test_expect_success 'fsck rejects relative URL resolving to empty scheme' '
453c5b
+	git checkout --orphan relative-empty-scheme &&
453c5b
+	cat >.gitmodules <<-\EOF &&
453c5b
+	[submodule "foo"]
453c5b
+		url = ../../../:://one.example.com/foo.git
453c5b
+	EOF
453c5b
+	git add .gitmodules &&
453c5b
+	test_tick &&
453c5b
+	git commit -m "relative gitmodules URL resolving to empty scheme" &&
453c5b
+	test_when_finished "rm -rf dst" &&
453c5b
+	git init --bare dst &&
453c5b
+	( cd dst && git config transfer.fsckObjects true ) &&
453c5b
+	test_must_fail git push dst HEAD 2>err &&
453c5b
+	grep "disallowed submodule url" err
453c5b
+'
453c5b
+
453c5b
 test_expect_success 'fsck permits embedded newline with unrecognized scheme' '
453c5b
 	git checkout --orphan newscheme &&
453c5b
 	cat >.gitmodules <<-\EOF &&
453c5b
-- 
453c5b
2.26.2
453c5b
453c5b
453c5b
From e9d4a9cf1445cff676b0147578a62be6a916f675 Mon Sep 17 00:00:00 2001
453c5b
From: Jonathan Nieder <jrnieder@gmail.com>
453c5b
Date: Sat, 18 Apr 2020 20:57:22 -0700
453c5b
Subject: [PATCH 11/11] fsck: reject URL with empty host in .gitmodules
453c5b
453c5b
Git's URL parser interprets
453c5b
453c5b
	https:///example.com/repo.git
453c5b
453c5b
to have no host and a path of "example.com/repo.git".  Curl, on the
453c5b
other hand, internally redirects it to https://example.com/repo.git.  As
453c5b
a result, until "credential: parse URL without host as empty host, not
453c5b
unset", tricking a user into fetching from such a URL would cause Git to
453c5b
send credentials for another host to example.com.
453c5b
453c5b
Teach fsck to block and detect .gitmodules files using such a URL to
453c5b
prevent sharing them with Git versions that are not yet protected.
453c5b
453c5b
A relative URL in a .gitmodules file could also be used to trigger this.
453c5b
The relative URL resolver used for .gitmodules does not normalize
453c5b
sequences of slashes and can follow ".." components out of the path part
453c5b
and to the host part of a URL, meaning that such a relative URL can be
453c5b
used to traverse from a https://foo.example.com/innocent superproject to
453c5b
a https:///attacker.example.com/exploit submodule. Fortunately,
453c5b
redundant extra slashes in .gitmodules are rare, so we can catch this by
453c5b
detecting one after a leading sequence of "./" and "../" components.
453c5b
453c5b
Helped-by: Jeff King <peff@peff.net>
453c5b
Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
453c5b
Reviewed-by: Jeff King <peff@peff.net>
453c5b
---
453c5b
 fsck.c                        | 10 +++++++---
453c5b
 t/t7416-submodule-dash-url.sh | 32 ++++++++++++++++++++++++++++++++
453c5b
 2 files changed, 39 insertions(+), 3 deletions(-)
453c5b
453c5b
diff --git a/fsck.c b/fsck.c
453c5b
index 90aea117c9..b5e0a79cda 100644
453c5b
--- a/fsck.c
453c5b
+++ b/fsck.c
453c5b
@@ -542,17 +542,21 @@ static int check_submodule_url(const char *url)
453c5b
 		/*
453c5b
 		 * URLs which escape their root via "../" can overwrite
453c5b
 		 * the host field and previous components, resolving to
453c5b
-		 * URLs like https::example.com/submodule.git that were
453c5b
+		 * URLs like https::example.com/submodule.git and
453c5b
+		 * https:///example.com/submodule.git that were
453c5b
 		 * susceptible to CVE-2020-11008.
453c5b
 		 */
453c5b
 		if (count_leading_dotdots(url, &next) > 0 &&
453c5b
-		    *next == ':')
453c5b
+		    (*next == ':' || *next == '/'))
453c5b
 			return -1;
453c5b
 	}
453c5b
 
453c5b
 	else if (url_to_curl_url(url, &curl_url)) {
453c5b
 		struct credential c = CREDENTIAL_INIT;
453c5b
-		int ret = credential_from_url_gently(&c, curl_url, 1);
453c5b
+		int ret = 0;
453c5b
+		if (credential_from_url_gently(&c, curl_url, 1) ||
453c5b
+		    !*c.host)
453c5b
+			ret = -1;
453c5b
 		credential_clear(&c);
453c5b
 		return ret;
453c5b
 	}
453c5b
diff --git a/t/t7416-submodule-dash-url.sh b/t/t7416-submodule-dash-url.sh
453c5b
index 9c7a8113fa..e65238f3c5 100755
453c5b
--- a/t/t7416-submodule-dash-url.sh
453c5b
+++ b/t/t7416-submodule-dash-url.sh
453c5b
@@ -110,6 +110,38 @@ test_expect_success 'fsck rejects relative URL resolving to empty scheme' '
453c5b
 	grep "disallowed submodule url" err
453c5b
 '
453c5b
 
453c5b
+test_expect_success 'fsck rejects empty hostname' '
453c5b
+	git checkout --orphan empty-host &&
453c5b
+	cat >.gitmodules <<-\EOF &&
453c5b
+	[submodule "foo"]
453c5b
+		url = http:///one.example.com/foo.git
453c5b
+	EOF
453c5b
+	git add .gitmodules &&
453c5b
+	test_tick &&
453c5b
+	git commit -m "gitmodules with extra slashes" &&
453c5b
+	test_when_finished "rm -rf dst" &&
453c5b
+	git init --bare dst &&
453c5b
+	( cd dst && git config transfer.fsckObjects true ) &&
453c5b
+	test_must_fail git push dst HEAD 2>err &&
453c5b
+	grep "disallowed submodule url" err
453c5b
+'
453c5b
+
453c5b
+test_expect_success 'fsck rejects relative url that produced empty hostname' '
453c5b
+	git checkout --orphan messy-relative &&
453c5b
+	cat >.gitmodules <<-\EOF &&
453c5b
+	[submodule "foo"]
453c5b
+		url = ../../..//one.example.com/foo.git
453c5b
+	EOF
453c5b
+	git add .gitmodules &&
453c5b
+	test_tick &&
453c5b
+	git commit -m "gitmodules abusing relative_path" &&
453c5b
+	test_when_finished "rm -rf dst" &&
453c5b
+	git init --bare dst &&
453c5b
+	( cd dst && git config transfer.fsckObjects true ) &&
453c5b
+	test_must_fail git push dst HEAD 2>err &&
453c5b
+	grep "disallowed submodule url" err
453c5b
+'
453c5b
+
453c5b
 test_expect_success 'fsck permits embedded newline with unrecognized scheme' '
453c5b
 	git checkout --orphan newscheme &&
453c5b
 	cat >.gitmodules <<-\EOF &&
453c5b
-- 
453c5b
2.26.2
453c5b