Blame SOURCES/git-cve-2018-11235.patch

1353ca
From 17f3a3cfacbc2a8c212062f3bbb1b03d668189c0 Mon Sep 17 00:00:00 2001
1353ca
From: Jeff King <peff@peff.net>
1353ca
Date: Mon, 30 Apr 2018 03:25:25 -0400
1353ca
Subject: [PATCH 01/11] submodule-config: verify submodule names as paths
1353ca
1353ca
Submodule "names" come from the untrusted .gitmodules file,
1353ca
but we blindly append them to $GIT_DIR/modules to create our
1353ca
on-disk repo paths. This means you can do bad things by
1353ca
putting "../" into the name (among other things).
1353ca
1353ca
Let's sanity-check these names to avoid building a path that
1353ca
can be exploited. There are two main decisions:
1353ca
1353ca
  1. What should the allowed syntax be?
1353ca
1353ca
     It's tempting to reuse verify_path(), since submodule
1353ca
     names typically come from in-repo paths. But there are
1353ca
     two reasons not to:
1353ca
1353ca
       a. It's technically more strict than what we need, as
1353ca
          we really care only about breaking out of the
1353ca
          $GIT_DIR/modules/ hierarchy.  E.g., having a
1353ca
          submodule named "foo/.git" isn't actually
1353ca
          dangerous, and it's possible that somebody has
1353ca
          manually given such a funny name.
1353ca
1353ca
       b. Since we'll eventually use this checking logic in
1353ca
          fsck to prevent downstream repositories, it should
1353ca
          be consistent across platforms. Because
1353ca
          verify_path() relies on is_dir_sep(), it wouldn't
1353ca
          block "foo\..\bar" on a non-Windows machine.
1353ca
1353ca
  2. Where should we enforce it? These days most of the
1353ca
     .gitmodules reads go through submodule-config.c, so
1353ca
     I've put it there in the reading step. That should
1353ca
     cover all of the C code.
1353ca
1353ca
     We also construct the name for "git submodule add"
1353ca
     inside the git-submodule.sh script. This is probably
1353ca
     not a big deal for security since the name is coming
1353ca
     from the user anyway, but it would be polite to remind
1353ca
     them if the name they pick is invalid (and we need to
1353ca
     expose the name-checker to the shell anyway for our
1353ca
     test scripts).
1353ca
1353ca
     This patch issues a warning when reading .gitmodules
1353ca
     and just ignores the related config entry completely.
1353ca
     This will generally end up producing a sensible error,
1353ca
     as it works the same as a .gitmodules file which is
1353ca
     missing a submodule entry (so "submodule update" will
1353ca
     barf, but "git clone --recurse-submodules" will print
1353ca
     an error but not abort the clone.
1353ca
1353ca
     There is one minor oddity, which is that we print the
1353ca
     warning once per malformed config key (since that's how
1353ca
     the config subsystem gives us the entries). So in the
1353ca
     new test, for example, the user would see three
1353ca
     warnings. That's OK, since the intent is that this case
1353ca
     should never come up outside of malicious repositories
1353ca
     (and then it might even benefit the user to see the
1353ca
     message multiple times).
1353ca
1353ca
Credit for finding this vulnerability and the proof of
1353ca
concept from which the test script was adapted goes to
1353ca
Etienne Stalmans.
1353ca
1353ca
Signed-off-by: Jeff King <peff@peff.net>
1353ca
---
1353ca
 builtin/submodule--helper.c | 27 +++++++++++++++-
1353ca
 git-submodule.sh            |  5 +++
1353ca
 submodule-config.c          | 31 ++++++++++++++++++
1353ca
 submodule-config.h          |  8 +++++
1353ca
 t/t7415-submodule-names.sh  | 76 +++++++++++++++++++++++++++++++++++++++++++++
1353ca
 5 files changed, 146 insertions(+), 1 deletion(-)
1353ca
 create mode 100755 t/t7415-submodule-names.sh
1353ca
1353ca
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
1353ca
index 926d205..5883e6c 100644
1353ca
--- a/builtin/submodule--helper.c
1353ca
+++ b/builtin/submodule--helper.c
1353ca
@@ -842,6 +842,30 @@ static int resolve_relative_path(int argc, const char **argv, const char *prefix
1353ca
 	return 0;
1353ca
 }
1353ca
 
1353ca
+/*
1353ca
+ * Exit non-zero if any of the submodule names given on the command line is
1353ca
+ * invalid. If no names are given, filter stdin to print only valid names
1353ca
+ * (which is primarily intended for testing).
1353ca
+ */
1353ca
+static int check_name(int argc, const char **argv, const char *prefix)
1353ca
+{
1353ca
+	if (argc > 1) {
1353ca
+		while (*++argv) {
1353ca
+			if (check_submodule_name(*argv) < 0)
1353ca
+				return 1;
1353ca
+		}
1353ca
+	} else {
1353ca
+		struct strbuf buf = STRBUF_INIT;
1353ca
+		while (strbuf_getline(&buf, stdin) != EOF) {
1353ca
+			if (!check_submodule_name(buf.buf))
1353ca
+				printf("%s\n", buf.buf);
1353ca
+		}
1353ca
+		strbuf_release(&buf;;
1353ca
+	}
1353ca
+	return 0;
1353ca
+}
1353ca
+
1353ca
+
1353ca
 struct cmd_struct {
1353ca
 	const char *cmd;
1353ca
 	int (*fn)(int, const char **, const char *);
1353ca
@@ -855,7 +879,8 @@ static struct cmd_struct commands[] = {
1353ca
 	{"relative-path", resolve_relative_path},
1353ca
 	{"resolve-relative-url", resolve_relative_url},
1353ca
 	{"resolve-relative-url-test", resolve_relative_url_test},
1353ca
-	{"init", module_init}
1353ca
+	{"init", module_init},
1353ca
+	{"check-name", check_name}
1353ca
 };
1353ca
 
1353ca
 int cmd_submodule__helper(int argc, const char **argv, const char *prefix)
1353ca
diff --git a/git-submodule.sh b/git-submodule.sh
1353ca
index 78fdac9..d82da42 100755
1353ca
--- a/git-submodule.sh
1353ca
+++ b/git-submodule.sh
1353ca
@@ -225,6 +225,11 @@ Use -f if you really want to add it." >&2
1353ca
 		sm_name="$sm_path"
1353ca
 	fi
1353ca
 
1353ca
+	if ! git submodule--helper check-name "$sm_name"
1353ca
+	then
1353ca
+		die "$(eval_gettext "'$sm_name' is not a valid submodule name")"
1353ca
+	fi
1353ca
+
1353ca
 	# perhaps the path exists and is already a git repo, else clone it
1353ca
 	if test -e "$sm_path"
1353ca
 	then
1353ca
diff --git a/submodule-config.c b/submodule-config.c
1353ca
index 93dd364..dbba74a 100644
1353ca
--- a/submodule-config.c
1353ca
+++ b/submodule-config.c
1353ca
@@ -162,6 +162,31 @@ static struct submodule *cache_lookup_name(struct submodule_cache *cache,
1353ca
 	return NULL;
1353ca
 }
1353ca
 
1353ca
+int check_submodule_name(const char *name)
1353ca
+{
1353ca
+	/* Disallow empty names */
1353ca
+	if (!*name)
1353ca
+		return -1;
1353ca
+
1353ca
+	/*
1353ca
+	 * Look for '..' as a path component. Check both '/' and '\\' as
1353ca
+	 * separators rather than is_dir_sep(), because we want the name rules
1353ca
+	 * to be consistent across platforms.
1353ca
+	 */
1353ca
+	goto in_component; /* always start inside component */
1353ca
+	while (*name) {
1353ca
+		char c = *name++;
1353ca
+		if (c == '/' || c == '\\') {
1353ca
+in_component:
1353ca
+			if (name[0] == '.' && name[1] == '.' &&
1353ca
+			    (!name[2] || name[2] == '/' || name[2] == '\\'))
1353ca
+				return -1;
1353ca
+		}
1353ca
+	}
1353ca
+
1353ca
+	return 0;
1353ca
+}
1353ca
+
1353ca
 static int name_and_item_from_var(const char *var, struct strbuf *name,
1353ca
 				  struct strbuf *item)
1353ca
 {
1353ca
@@ -173,6 +198,12 @@ static int name_and_item_from_var(const char *var, struct strbuf *name,
1353ca
 		return 0;
1353ca
 
1353ca
 	strbuf_add(name, subsection, subsection_len);
1353ca
+	if (check_submodule_name(name->buf) < 0) {
1353ca
+		warning(_("ignoring suspicious submodule name: %s"), name->buf);
1353ca
+		strbuf_release(name);
1353ca
+		return 0;
1353ca
+	}
1353ca
+
1353ca
 	strbuf_addstr(item, key);
1353ca
 
1353ca
 	return 1;
1353ca
diff --git a/submodule-config.h b/submodule-config.h
1353ca
index e4857f5..6ed7a3c 100644
1353ca
--- a/submodule-config.h
1353ca
+++ b/submodule-config.h
1353ca
@@ -29,4 +29,12 @@ const struct submodule *submodule_from_path(const unsigned char *commit_sha1,
1353ca
 		const char *path);
1353ca
 void submodule_free(void);
1353ca
 
1353ca
+/*
1353ca
+ * Returns 0 if the name is syntactically acceptable as a submodule "name"
1353ca
+ * (e.g., that may be found in the subsection of a .gitmodules file) and -1
1353ca
+ * otherwise.
1353ca
+ */
1353ca
+int check_submodule_name(const char *name);
1353ca
+
1353ca
+
1353ca
 #endif /* SUBMODULE_CONFIG_H */
1353ca
diff --git a/t/t7415-submodule-names.sh b/t/t7415-submodule-names.sh
1353ca
new file mode 100755
1353ca
index 0000000..75fa071
1353ca
--- /dev/null
1353ca
+++ b/t/t7415-submodule-names.sh
1353ca
@@ -0,0 +1,76 @@
1353ca
+#!/bin/sh
1353ca
+
1353ca
+test_description='check handling of .. in submodule names
1353ca
+
1353ca
+Exercise the name-checking function on a variety of names, and then give a
1353ca
+real-world setup that confirms we catch this in practice.
1353ca
+'
1353ca
+. ./test-lib.sh
1353ca
+
1353ca
+test_expect_success 'check names' '
1353ca
+	cat >expect <<-\EOF &&
1353ca
+	valid
1353ca
+	valid/with/paths
1353ca
+	EOF
1353ca
+
1353ca
+	git submodule--helper check-name >actual <<-\EOF &&
1353ca
+	valid
1353ca
+	valid/with/paths
1353ca
+
1353ca
+	../foo
1353ca
+	/../foo
1353ca
+	..\foo
1353ca
+	\..\foo
1353ca
+	foo/..
1353ca
+	foo/../
1353ca
+	foo\..
1353ca
+	foo\..\
1353ca
+	foo/../bar
1353ca
+	EOF
1353ca
+
1353ca
+	test_cmp expect actual
1353ca
+'
1353ca
+
1353ca
+test_expect_success 'create innocent subrepo' '
1353ca
+	git init innocent &&
1353ca
+	git -C innocent commit --allow-empty -m foo
1353ca
+'
1353ca
+
1353ca
+test_expect_success 'submodule add refuses invalid names' '
1353ca
+	test_must_fail \
1353ca
+		git submodule add --name ../../modules/evil "$PWD/innocent" evil
1353ca
+'
1353ca
+
1353ca
+test_expect_success 'add evil submodule' '
1353ca
+	git submodule add "$PWD/innocent" evil &&
1353ca
+
1353ca
+	mkdir modules &&
1353ca
+	cp -r .git/modules/evil modules &&
1353ca
+	write_script modules/evil/hooks/post-checkout <<-\EOF &&
1353ca
+	echo >&2 "RUNNING POST CHECKOUT"
1353ca
+	EOF
1353ca
+
1353ca
+	git config -f .gitmodules submodule.evil.update checkout &&
1353ca
+	git config -f .gitmodules --rename-section \
1353ca
+		submodule.evil submodule.../../modules/evil &&
1353ca
+	git add modules &&
1353ca
+	git commit -am evil
1353ca
+'
1353ca
+
1353ca
+# This step seems like it shouldn't be necessary, since the payload is
1353ca
+# contained entirely in the evil submodule. But due to the vagaries of the
1353ca
+# submodule code, checking out the evil module will fail unless ".git/modules"
1353ca
+# exists. Adding another submodule (with a name that sorts before "evil") is an
1353ca
+# easy way to make sure this is the case in the victim clone.
1353ca
+test_expect_success 'add other submodule' '
1353ca
+	git submodule add "$PWD/innocent" another-module &&
1353ca
+	git add another-module &&
1353ca
+	git commit -am another
1353ca
+'
1353ca
+
1353ca
+test_expect_success 'clone evil superproject' '
1353ca
+	git clone --recurse-submodules . victim >output 2>&1 &&
1353ca
+	! grep "RUNNING POST CHECKOUT" output
1353ca
+'
1353ca
+
1353ca
+test_done
1353ca
-- 
1353ca
2.14.4
1353ca
1353ca
1353ca
From 42410051db39f2009980ba293ff0f4f1755df06a Mon Sep 17 00:00:00 2001
1353ca
From: Jeff King <peff@peff.net>
1353ca
Date: Sun, 13 May 2018 12:09:42 -0400
1353ca
Subject: [PATCH 02/11] is_ntfs_dotgit: use a size_t for traversing string
1353ca
1353ca
We walk through the "name" string using an int, which can
1353ca
wrap to a negative value and cause us to read random memory
1353ca
before our array (e.g., by creating a tree with a name >2GB,
1353ca
since "int" is still 32 bits even on most 64-bit platforms).
1353ca
Worse, this is easy to trigger during the fsck_tree() check,
1353ca
which is supposed to be protecting us from malicious
1353ca
garbage.
1353ca
1353ca
Note one bit of trickiness in the existing code: we
1353ca
sometimes assign -1 to "len" at the end of the loop, and
1353ca
then rely on the "len++" in the for-loop's increment to take
1353ca
it back to 0. This is still legal with a size_t, since
1353ca
assigning -1 will turn into SIZE_MAX, which then wraps
1353ca
around to 0 on increment.
1353ca
1353ca
Signed-off-by: Jeff King <peff@peff.net>
1353ca
---
1353ca
 path.c | 2 +-
1353ca
 1 file changed, 1 insertion(+), 1 deletion(-)
1353ca
1353ca
diff --git a/path.c b/path.c
1353ca
index 0778ff0..3f41416 100644
1353ca
--- a/path.c
1353ca
+++ b/path.c
1353ca
@@ -1205,7 +1205,7 @@ static int only_spaces_and_periods(const char *path, size_t len, size_t skip)
1353ca
 
1353ca
 int is_ntfs_dotgit(const char *name)
1353ca
 {
1353ca
-	int len;
1353ca
+	size_t len;
1353ca
 
1353ca
 	for (len = 0; ; len++)
1353ca
 		if (!name[len] || name[len] == '\\' || is_dir_sep(name[len])) {
1353ca
-- 
1353ca
2.14.4
1353ca
1353ca
1353ca
From e63ce01d9a387c60754349508352f3bca086bbec Mon Sep 17 00:00:00 2001
1353ca
From: Jeff King <peff@peff.net>
1353ca
Date: Wed, 2 May 2018 15:23:45 -0400
1353ca
Subject: [PATCH 03/11] is_hfs_dotgit: match other .git files
1353ca
1353ca
Both verify_path() and fsck match ".git", ".GIT", and other
1353ca
variants specific to HFS+. Let's allow matching other
1353ca
special files like ".gitmodules", which we'll later use to
1353ca
enforce extra restrictions via verify_path() and fsck.
1353ca
1353ca
Signed-off-by: Jeff King <peff@peff.net>
1353ca
---
1353ca
 utf8.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++------------
1353ca
 utf8.h |  5 +++++
1353ca
 2 files changed, 51 insertions(+), 12 deletions(-)
1353ca
1353ca
diff --git a/utf8.c b/utf8.c
1353ca
index 00e10c8..0d406b1 100644
1353ca
--- a/utf8.c
1353ca
+++ b/utf8.c
1353ca
@@ -605,28 +605,33 @@ static ucs_char_t next_hfs_char(const char **in)
1353ca
 	}
1353ca
 }
1353ca
 
1353ca
-int is_hfs_dotgit(const char *path)
1353ca
+static int is_hfs_dot_generic(const char *path,
1353ca
+			      const char *needle, size_t needle_len)
1353ca
 {
1353ca
 	ucs_char_t c;
1353ca
 
1353ca
 	c = next_hfs_char(&path);
1353ca
 	if (c != '.')
1353ca
 		return 0;
1353ca
-	c = next_hfs_char(&path);
1353ca
 
1353ca
 	/*
1353ca
 	 * there's a great deal of other case-folding that occurs
1353ca
-	 * in HFS+, but this is enough to catch anything that will
1353ca
-	 * convert to ".git"
1353ca
+	 * in HFS+, but this is enough to catch our fairly vanilla
1353ca
+	 * hard-coded needles.
1353ca
 	 */
1353ca
-	if (c != 'g' && c != 'G')
1353ca
-		return 0;
1353ca
-	c = next_hfs_char(&path);
1353ca
-	if (c != 'i' && c != 'I')
1353ca
-		return 0;
1353ca
-	c = next_hfs_char(&path);
1353ca
-	if (c != 't' && c != 'T')
1353ca
-		return 0;
1353ca
+	for (; needle_len > 0; needle++, needle_len--) {
1353ca
+		c = next_hfs_char(&path);
1353ca
+
1353ca
+		/*
1353ca
+		 * We know our needles contain only ASCII, so we clamp here to
1353ca
+		 * make the results of tolower() sane.
1353ca
+		 */
1353ca
+		if (c > 127)
1353ca
+			return 0;
1353ca
+		if (tolower(c) != *needle)
1353ca
+			return 0;
1353ca
+	}
1353ca
+
1353ca
 	c = next_hfs_char(&path);
1353ca
 	if (c && !is_dir_sep(c))
1353ca
 		return 0;
1353ca
@@ -634,6 +639,35 @@ int is_hfs_dotgit(const char *path)
1353ca
 	return 1;
1353ca
 }
1353ca
 
1353ca
+/*
1353ca
+ * Inline wrapper to make sure the compiler resolves strlen() on literals at
1353ca
+ * compile time.
1353ca
+ */
1353ca
+static inline int is_hfs_dot_str(const char *path, const char *needle)
1353ca
+{
1353ca
+	return is_hfs_dot_generic(path, needle, strlen(needle));
1353ca
+}
1353ca
+
1353ca
+int is_hfs_dotgit(const char *path)
1353ca
+{
1353ca
+	return is_hfs_dot_str(path, "git");
1353ca
+}
1353ca
+
1353ca
+int is_hfs_dotgitmodules(const char *path)
1353ca
+{
1353ca
+	return is_hfs_dot_str(path, "gitmodules");
1353ca
+}
1353ca
+
1353ca
+int is_hfs_dotgitignore(const char *path)
1353ca
+{
1353ca
+	return is_hfs_dot_str(path, "gitignore");
1353ca
+}
1353ca
+
1353ca
+int is_hfs_dotgitattributes(const char *path)
1353ca
+{
1353ca
+	return is_hfs_dot_str(path, "gitattributes");
1353ca
+}
1353ca
+
1353ca
 const char utf8_bom[] = "\357\273\277";
1353ca
 
1353ca
 int skip_utf8_bom(char **text, size_t len)
1353ca
diff --git a/utf8.h b/utf8.h
1353ca
index 6bbcf31..da19b43 100644
1353ca
--- a/utf8.h
1353ca
+++ b/utf8.h
1353ca
@@ -52,8 +52,13 @@ int mbs_chrlen(const char **text, size_t *remainder_p, const char *encoding);
1353ca
  * The path should be NUL-terminated, but we will match variants of both ".git\0"
1353ca
  * and ".git/..." (but _not_ ".../.git"). This makes it suitable for both fsck
1353ca
  * and verify_path().
1353ca
+ *
1353ca
+ * Likewise, the is_hfs_dotgitfoo() variants look for ".gitfoo".
1353ca
  */
1353ca
 int is_hfs_dotgit(const char *path);
1353ca
+int is_hfs_dotgitmodules(const char *path);
1353ca
+int is_hfs_dotgitignore(const char *path);
1353ca
+int is_hfs_dotgitattributes(const char *path);
1353ca
 
1353ca
 typedef enum {
1353ca
 	ALIGN_LEFT,
1353ca
-- 
1353ca
2.14.4
1353ca
1353ca
1353ca
From c914f82f17ce42ea03ae1754f263c600e4faa7f3 Mon Sep 17 00:00:00 2001
1353ca
From: Johannes Schindelin <johannes.schindelin@gmx.de>
1353ca
Date: Fri, 11 May 2018 16:03:54 +0200
1353ca
Subject: [PATCH 04/11] is_ntfs_dotgit: match other .git files
1353ca
1353ca
When we started to catch NTFS short names that clash with .git, we only
1353ca
looked for GIT~1. This is sufficient because we only ever clone into an
1353ca
empty directory, so .git is guaranteed to be the first subdirectory or
1353ca
file in that directory.
1353ca
1353ca
However, even with a fresh clone, .gitmodules is *not* necessarily the
1353ca
first file to be written that would want the NTFS short name GITMOD~1: a
1353ca
malicious repository can add .gitmodul0000 and friends, which sorts
1353ca
before `.gitmodules` and is therefore checked out *first*. For that
1353ca
reason, we have to test not only for ~1 short names, but for others,
1353ca
too.
1353ca
1353ca
It's hard to just adapt the existing checks in is_ntfs_dotgit(): since
1353ca
Windows 2000 (i.e., in all Windows versions still supported by Git),
1353ca
NTFS short names are only generated in the <prefix>~<number> form up to
1353ca
number 4. After that, a *different* prefix is used, calculated from the
1353ca
long file name using an undocumented, but stable algorithm.
1353ca
1353ca
For example, the short name of .gitmodules would be GITMOD~1, but if it
1353ca
is taken, and all of ~2, ~3 and ~4 are taken, too, the short name
1353ca
GI7EBA~1 will be used. From there, collisions are handled by
1353ca
incrementing the number, shortening the prefix as needed (until ~9999999
1353ca
is reached, in which case NTFS will not allow the file to be created).
1353ca
1353ca
We'd also want to handle .gitignore and .gitattributes, which suffer
1353ca
from a similar problem, using the fall-back short names GI250A~1 and
1353ca
GI7D29~1, respectively.
1353ca
1353ca
To accommodate for that, we could reimplement the hashing algorithm, but
1353ca
it is just safer and simpler to provide the known prefixes. This
1353ca
algorithm has been reverse-engineered and described at
1353ca
https://usn.pw/blog/gen/2015/06/09/filenames/, which is defunct but
1353ca
still available via https://web.archive.org/.
1353ca
1353ca
These can be recomputed by running the following Perl script:
1353ca
1353ca
-- snip --
1353ca
use warnings;
1353ca
use strict;
1353ca
1353ca
sub compute_short_name_hash ($) {
1353ca
        my $checksum = 0;
1353ca
        foreach (split('', $_[0])) {
1353ca
                $checksum = ($checksum * 0x25 + ord($_)) & 0xffff;
1353ca
        }
1353ca
1353ca
        $checksum = ($checksum * 314159269) & 0xffffffff;
1353ca
        $checksum = 1 + (~$checksum & 0x7fffffff) if ($checksum & 0x80000000);
1353ca
        $checksum -= (($checksum * 1152921497) >> 60) * 1000000007;
1353ca
1353ca
        return scalar reverse sprintf("%x", $checksum & 0xffff);
1353ca
}
1353ca
1353ca
print compute_short_name_hash($ARGV[0]);
1353ca
-- snap --
1353ca
1353ca
E.g., running that with the argument ".gitignore" will
1353ca
result in "250a" (which then becomes "gi250a" in the code).
1353ca
1353ca
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
1353ca
Signed-off-by: Jeff King <peff@peff.net>
1353ca
---
1353ca
 cache.h | 10 +++++++-
1353ca
 path.c  | 84 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1353ca
 2 files changed, 93 insertions(+), 1 deletion(-)
1353ca
1353ca
diff --git a/cache.h b/cache.h
1353ca
index 17afd25..fbbe933 100644
1353ca
--- a/cache.h
1353ca
+++ b/cache.h
1353ca
@@ -1032,7 +1032,15 @@ int normalize_path_copy(char *dst, const char *src);
1353ca
 int longest_ancestor_length(const char *path, struct string_list *prefixes);
1353ca
 char *strip_path_suffix(const char *path, const char *suffix);
1353ca
 int daemon_avoid_alias(const char *path);
1353ca
-extern int is_ntfs_dotgit(const char *name);
1353ca
+
1353ca
+/*
1353ca
+ * These functions match their is_hfs_dotgit() counterparts; see utf8.h for
1353ca
+ * details.
1353ca
+ */
1353ca
+int is_ntfs_dotgit(const char *name);
1353ca
+int is_ntfs_dotgitmodules(const char *name);
1353ca
+int is_ntfs_dotgitignore(const char *name);
1353ca
+int is_ntfs_dotgitattributes(const char *name);
1353ca
 
1353ca
 /*
1353ca
  * Returns true iff "str" could be confused as a command-line option when
1353ca
diff --git a/path.c b/path.c
1353ca
index 3f41416..34d95a4 100644
1353ca
--- a/path.c
1353ca
+++ b/path.c
1353ca
@@ -1222,6 +1222,90 @@ int is_ntfs_dotgit(const char *name)
1353ca
 		}
1353ca
 }
1353ca
 
1353ca
+static int is_ntfs_dot_generic(const char *name,
1353ca
+			       const char *dotgit_name,
1353ca
+			       size_t len,
1353ca
+			       const char *dotgit_ntfs_shortname_prefix)
1353ca
+{
1353ca
+	int saw_tilde;
1353ca
+	size_t i;
1353ca
+
1353ca
+	if ((name[0] == '.' && !strncasecmp(name + 1, dotgit_name, len))) {
1353ca
+		i = len + 1;
1353ca
+only_spaces_and_periods:
1353ca
+		for (;;) {
1353ca
+			char c = name[i++];
1353ca
+			if (!c)
1353ca
+				return 1;
1353ca
+			if (c != ' ' && c != '.')
1353ca
+				return 0;
1353ca
+		}
1353ca
+	}
1353ca
+
1353ca
+	/*
1353ca
+	 * Is it a regular NTFS short name, i.e. shortened to 6 characters,
1353ca
+	 * followed by ~1, ... ~4?
1353ca
+	 */
1353ca
+	if (!strncasecmp(name, dotgit_name, 6) && name[6] == '~' &&
1353ca
+	    name[7] >= '1' && name[7] <= '4') {
1353ca
+		i = 8;
1353ca
+		goto only_spaces_and_periods;
1353ca
+	}
1353ca
+
1353ca
+	/*
1353ca
+	 * Is it a fall-back NTFS short name (for details, see
1353ca
+	 * https://en.wikipedia.org/wiki/8.3_filename?
1353ca
+	 */
1353ca
+	for (i = 0, saw_tilde = 0; i < 8; i++)
1353ca
+		if (name[i] == '\0')
1353ca
+			return 0;
1353ca
+		else if (saw_tilde) {
1353ca
+			if (name[i] < '0' || name[i] > '9')
1353ca
+				return 0;
1353ca
+		} else if (name[i] == '~') {
1353ca
+			if (name[++i] < '1' || name[i] > '9')
1353ca
+				return 0;
1353ca
+			saw_tilde = 1;
1353ca
+		} else if (i >= 6)
1353ca
+			return 0;
1353ca
+		else if (name[i] < 0) {
1353ca
+			/*
1353ca
+			 * We know our needles contain only ASCII, so we clamp
1353ca
+			 * here to make the results of tolower() sane.
1353ca
+			 */
1353ca
+			return 0;
1353ca
+		} else if (tolower(name[i]) != dotgit_ntfs_shortname_prefix[i])
1353ca
+			return 0;
1353ca
+
1353ca
+	goto only_spaces_and_periods;
1353ca
+}
1353ca
+
1353ca
+/*
1353ca
+ * Inline helper to make sure compiler resolves strlen() on literals at
1353ca
+ * compile time.
1353ca
+ */
1353ca
+static inline int is_ntfs_dot_str(const char *name, const char *dotgit_name,
1353ca
+				  const char *dotgit_ntfs_shortname_prefix)
1353ca
+{
1353ca
+	return is_ntfs_dot_generic(name, dotgit_name, strlen(dotgit_name),
1353ca
+				   dotgit_ntfs_shortname_prefix);
1353ca
+}
1353ca
+
1353ca
+int is_ntfs_dotgitmodules(const char *name)
1353ca
+{
1353ca
+	return is_ntfs_dot_str(name, "gitmodules", "gi7eba");
1353ca
+}
1353ca
+
1353ca
+int is_ntfs_dotgitignore(const char *name)
1353ca
+{
1353ca
+	return is_ntfs_dot_str(name, "gitignore", "gi250a");
1353ca
+}
1353ca
+
1353ca
+int is_ntfs_dotgitattributes(const char *name)
1353ca
+{
1353ca
+	return is_ntfs_dot_str(name, "gitattributes", "gi7d29");
1353ca
+}
1353ca
+
1353ca
 int looks_like_command_line_option(const char *str)
1353ca
 {
1353ca
 	return str && str[0] == '-';
1353ca
-- 
1353ca
2.14.4
1353ca
1353ca
1353ca
From 4c8e72db9864e150f163d1b9da7b0e4e15f49585 Mon Sep 17 00:00:00 2001
1353ca
From: Johannes Schindelin <johannes.schindelin@gmx.de>
1353ca
Date: Sat, 12 May 2018 22:16:51 +0200
1353ca
Subject: [PATCH 05/11] is_{hfs,ntfs}_dotgitmodules: add tests
1353ca
1353ca
This tests primarily for NTFS issues, but also adds one example of an
1353ca
HFS+ issue.
1353ca
1353ca
Thanks go to Congyi Wu for coming up with the list of examples where
1353ca
NTFS would possibly equate the filename with `.gitmodules`.
1353ca
1353ca
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
1353ca
Signed-off-by: Jeff King <peff@peff.net>
1353ca
---
1353ca
 t/helper/test-path-utils.c | 20 +++++++++++
1353ca
 t/t0060-path-utils.sh      | 86 ++++++++++++++++++++++++++++++++++++++++++++++
1353ca
 2 files changed, 106 insertions(+)
1353ca
1353ca
diff --git a/t/helper/test-path-utils.c b/t/helper/test-path-utils.c
1353ca
index ba805b3..e75b146 100644
1353ca
--- a/t/helper/test-path-utils.c
1353ca
+++ b/t/helper/test-path-utils.c
1353ca
@@ -1,5 +1,6 @@
1353ca
 #include "cache.h"
1353ca
 #include "string-list.h"
1353ca
+#include "utf8.h"
1353ca
 
1353ca
 /*
1353ca
  * A "string_list_each_func_t" function that normalizes an entry from
1353ca
@@ -156,6 +157,11 @@ static struct test_data dirname_data[] = {
1353ca
 	{ NULL,              NULL     }
1353ca
 };
1353ca
 
1353ca
+static int is_dotgitmodules(const char *path)
1353ca
+{
1353ca
+	return is_hfs_dotgitmodules(path) || is_ntfs_dotgitmodules(path);
1353ca
+}
1353ca
+
1353ca
 int main(int argc, char **argv)
1353ca
 {
1353ca
 	if (argc == 3 && !strcmp(argv[1], "normalize_path_copy")) {
1353ca
@@ -256,6 +262,20 @@ int main(int argc, char **argv)
1353ca
 	if (argc == 2 && !strcmp(argv[1], "dirname"))
1353ca
 		return test_function(dirname_data, dirname, argv[1]);
1353ca
 
1353ca
+	if (argc > 2 && !strcmp(argv[1], "is_dotgitmodules")) {
1353ca
+		int res = 0, expect = 1, i;
1353ca
+		for (i = 2; i < argc; i++)
1353ca
+			if (!strcmp("--not", argv[i]))
1353ca
+				expect = !expect;
1353ca
+			else if (expect != is_dotgitmodules(argv[i]))
1353ca
+				res = error("'%s' is %s.gitmodules", argv[i],
1353ca
+					    expect ? "not " : "");
1353ca
+			else
1353ca
+				fprintf(stderr, "ok: '%s' is %s.gitmodules\n",
1353ca
+					argv[i], expect ? "" : "not ");
1353ca
+		return !!res;
1353ca
+	}
1353ca
+
1353ca
 	fprintf(stderr, "%s: unknown function name: %s\n", argv[0],
1353ca
 		argv[1] ? argv[1] : "(there was none)");
1353ca
 	return 1;
1353ca
diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh
1353ca
index bf2deee..a1c9bbd 100755
1353ca
--- a/t/t0060-path-utils.sh
1353ca
+++ b/t/t0060-path-utils.sh
1353ca
@@ -344,4 +344,90 @@ test_submodule_relative_url "(null)" "ssh://hostname:22/repo" "../subrepo" "ssh:
1353ca
 test_submodule_relative_url "(null)" "user@host:path/to/repo" "../subrepo" "user@host:path/to/subrepo"
1353ca
 test_submodule_relative_url "(null)" "user@host:repo" "../subrepo" "user@host:subrepo"
1353ca
 
1353ca
+test_expect_success 'match .gitmodules' '
1353ca
+	test-path-utils is_dotgitmodules \
1353ca
+		.gitmodules \
1353ca
+		\
1353ca
+		.git${u200c}modules \
1353ca
+		\
1353ca
+		.Gitmodules \
1353ca
+		.gitmoduleS \
1353ca
+		\
1353ca
+		".gitmodules " \
1353ca
+		".gitmodules." \
1353ca
+		".gitmodules  " \
1353ca
+		".gitmodules. " \
1353ca
+		".gitmodules ." \
1353ca
+		".gitmodules.." \
1353ca
+		".gitmodules   " \
1353ca
+		".gitmodules.  " \
1353ca
+		".gitmodules . " \
1353ca
+		".gitmodules  ." \
1353ca
+		\
1353ca
+		".Gitmodules " \
1353ca
+		".Gitmodules." \
1353ca
+		".Gitmodules  " \
1353ca
+		".Gitmodules. " \
1353ca
+		".Gitmodules ." \
1353ca
+		".Gitmodules.." \
1353ca
+		".Gitmodules   " \
1353ca
+		".Gitmodules.  " \
1353ca
+		".Gitmodules . " \
1353ca
+		".Gitmodules  ." \
1353ca
+		\
1353ca
+		GITMOD~1 \
1353ca
+		gitmod~1 \
1353ca
+		GITMOD~2 \
1353ca
+		gitmod~3 \
1353ca
+		GITMOD~4 \
1353ca
+		\
1353ca
+		"GITMOD~1 " \
1353ca
+		"gitmod~2." \
1353ca
+		"GITMOD~3  " \
1353ca
+		"gitmod~4. " \
1353ca
+		"GITMOD~1 ." \
1353ca
+		"gitmod~2   " \
1353ca
+		"GITMOD~3.  " \
1353ca
+		"gitmod~4 . " \
1353ca
+		\
1353ca
+		GI7EBA~1 \
1353ca
+		gi7eba~9 \
1353ca
+		\
1353ca
+		GI7EB~10 \
1353ca
+		GI7EB~11 \
1353ca
+		GI7EB~99 \
1353ca
+		GI7EB~10 \
1353ca
+		GI7E~100 \
1353ca
+		GI7E~101 \
1353ca
+		GI7E~999 \
1353ca
+		~1000000 \
1353ca
+		~9999999 \
1353ca
+		\
1353ca
+		--not \
1353ca
+		".gitmodules x"  \
1353ca
+		".gitmodules .x" \
1353ca
+		\
1353ca
+		" .gitmodules" \
1353ca
+		\
1353ca
+		..gitmodules \
1353ca
+		\
1353ca
+		gitmodules \
1353ca
+		\
1353ca
+		.gitmodule \
1353ca
+		\
1353ca
+		".gitmodules x " \
1353ca
+		".gitmodules .x" \
1353ca
+		\
1353ca
+		GI7EBA~ \
1353ca
+		GI7EBA~0 \
1353ca
+		GI7EBA~~1 \
1353ca
+		GI7EBA~X \
1353ca
+		Gx7EBA~1 \
1353ca
+		GI7EBX~1 \
1353ca
+		\
1353ca
+		GI7EB~1 \
1353ca
+		GI7EB~01 \
1353ca
+		GI7EB~1X
1353ca
+'
1353ca
+
1353ca
 test_done
1353ca
-- 
1353ca
2.14.4
1353ca
1353ca
1353ca
From f57dd60d921e31b78e42ad9f91c333ab19138deb Mon Sep 17 00:00:00 2001
1353ca
From: Jeff King <peff@peff.net>
1353ca
Date: Sun, 13 May 2018 12:57:14 -0400
1353ca
Subject: [PATCH 06/11] skip_prefix: add case-insensitive variant
1353ca
1353ca
We have the convenient skip_prefix() helper, but if you want
1353ca
to do case-insensitive matching, you're stuck doing it by
1353ca
hand. We could add an extra parameter to the function to
1353ca
let callers ask for this, but the function is small and
1353ca
somewhat performance-critical. Let's just re-implement it
1353ca
for the case-insensitive version.
1353ca
1353ca
Signed-off-by: Jeff King <peff@peff.net>
1353ca
---
1353ca
 git-compat-util.h | 17 +++++++++++++++++
1353ca
 1 file changed, 17 insertions(+)
1353ca
1353ca
diff --git a/git-compat-util.h b/git-compat-util.h
1353ca
index 49d4029..7ad6e59 100644
1353ca
--- a/git-compat-util.h
1353ca
+++ b/git-compat-util.h
1353ca
@@ -909,6 +909,23 @@ static inline int sane_iscase(int x, int is_lower)
1353ca
 		return (x & 0x20) == 0;
1353ca
 }
1353ca
 
1353ca
+/*
1353ca
+ * Like skip_prefix, but compare case-insensitively. Note that the comparison
1353ca
+ * is done via tolower(), so it is strictly ASCII (no multi-byte characters or
1353ca
+ * locale-specific conversions).
1353ca
+ */
1353ca
+static inline int skip_iprefix(const char *str, const char *prefix,
1353ca
+			       const char **out)
1353ca
+{
1353ca
+	do {
1353ca
+		if (!*prefix) {
1353ca
+			*out = str;
1353ca
+			return 1;
1353ca
+		}
1353ca
+	} while (tolower(*str++) == tolower(*prefix++));
1353ca
+	return 0;
1353ca
+}
1353ca
+
1353ca
 static inline int strtoul_ui(char const *s, int base, unsigned int *result)
1353ca
 {
1353ca
 	unsigned long ul;
1353ca
-- 
1353ca
2.14.4
1353ca
1353ca
1353ca
From da0dec01dc5fb1d43732ad997d30bed853025a6c Mon Sep 17 00:00:00 2001
1353ca
From: Jeff King <peff@peff.net>
1353ca
Date: Sun, 13 May 2018 13:00:23 -0400
1353ca
Subject: [PATCH 07/11] verify_path: drop clever fallthrough
1353ca
1353ca
We check ".git" and ".." in the same switch statement, and
1353ca
fall through the cases to share the end-of-component check.
1353ca
While this saves us a line or two, it makes modifying the
1353ca
function much harder. Let's just write it out.
1353ca
1353ca
Signed-off-by: Jeff King <peff@peff.net>
1353ca
---
1353ca
 read-cache.c | 8 ++++----
1353ca
 1 file changed, 4 insertions(+), 4 deletions(-)
1353ca
1353ca
diff --git a/read-cache.c b/read-cache.c
1353ca
index db27766..b8a2a06 100644
1353ca
--- a/read-cache.c
1353ca
+++ b/read-cache.c
1353ca
@@ -787,8 +787,7 @@ static int verify_dotfile(const char *rest)
1353ca
 
1353ca
 	switch (*rest) {
1353ca
 	/*
1353ca
-	 * ".git" followed by  NUL or slash is bad. This
1353ca
-	 * shares the path end test with the ".." case.
1353ca
+	 * ".git" followed by NUL or slash is bad.
1353ca
 	 */
1353ca
 	case 'g':
1353ca
 	case 'G':
1353ca
@@ -796,8 +795,9 @@ static int verify_dotfile(const char *rest)
1353ca
 			break;
1353ca
 		if (rest[2] != 't' && rest[2] != 'T')
1353ca
 			break;
1353ca
-		rest += 2;
1353ca
-	/* fallthrough */
1353ca
+		if (rest[3] == '\0' || is_dir_sep(rest[3]))
1353ca
+			return 0;
1353ca
+		break;
1353ca
 	case '.':
1353ca
 		if (rest[1] == '\0' || is_dir_sep(rest[1]))
1353ca
 			return 0;
1353ca
-- 
1353ca
2.14.4
1353ca
1353ca
1353ca
From 392a029f659dc735a445ca16349b98c0c63bcee6 Mon Sep 17 00:00:00 2001
1353ca
From: Jeff King <peff@peff.net>
1353ca
Date: Tue, 15 May 2018 09:56:50 -0400
1353ca
Subject: [PATCH 08/11] verify_dotfile: mention case-insensitivity in comment
1353ca
1353ca
We're more restrictive than we need to be in matching ".GIT"
1353ca
on case-sensitive filesystems; let's make a note that this
1353ca
is intentional.
1353ca
1353ca
Signed-off-by: Jeff King <peff@peff.net>
1353ca
---
1353ca
 read-cache.c | 5 ++++-
1353ca
 1 file changed, 4 insertions(+), 1 deletion(-)
1353ca
1353ca
diff --git a/read-cache.c b/read-cache.c
1353ca
index b8a2a06..88ed49c 100644
1353ca
--- a/read-cache.c
1353ca
+++ b/read-cache.c
1353ca
@@ -787,7 +787,10 @@ static int verify_dotfile(const char *rest)
1353ca
 
1353ca
 	switch (*rest) {
1353ca
 	/*
1353ca
-	 * ".git" followed by NUL or slash is bad.
1353ca
+	 * ".git" followed by NUL or slash is bad. Note that we match
1353ca
+	 * case-insensitively here, even if ignore_case is not set.
1353ca
+	 * This outlaws ".GIT" everywhere out of an abundance of caution,
1353ca
+	 * since there's really no good reason to allow it.
1353ca
 	 */
1353ca
 	case 'g':
1353ca
 	case 'G':
1353ca
-- 
1353ca
2.14.4
1353ca
1353ca
1353ca
From 95a1ce8bd49ad78d9266af969fa6a9b5476ad118 Mon Sep 17 00:00:00 2001
1353ca
From: Jeff King <peff@peff.net>
1353ca
Date: Mon, 14 May 2018 11:00:56 -0400
1353ca
Subject: [PATCH 09/11] update-index: stat updated files earlier
1353ca
1353ca
In the update_one(), we check verify_path() on the proposed
1353ca
path before doing anything else. In preparation for having
1353ca
verify_path() look at the file mode, let's stat the file
1353ca
earlier, so we can check the mode accurately.
1353ca
1353ca
This is made a bit trickier by the fact that this function
1353ca
only does an lstat in a few code paths (the ones that flow
1353ca
down through process_path()). So we can speculatively do the
1353ca
lstat() here and pass the results down, and just use a dummy
1353ca
mode for cases where we won't actually be updating the index
1353ca
from the filesystem.
1353ca
1353ca
Signed-off-by: Jeff King <peff@peff.net>
1353ca
---
1353ca
 builtin/update-index.c | 25 +++++++++++++++++--------
1353ca
 1 file changed, 17 insertions(+), 8 deletions(-)
1353ca
1353ca
diff --git a/builtin/update-index.c b/builtin/update-index.c
1353ca
index b8b8522..0c5be84 100644
1353ca
--- a/builtin/update-index.c
1353ca
+++ b/builtin/update-index.c
1353ca
@@ -354,10 +354,9 @@ static int process_directory(const char *path, int len, struct stat *st)
1353ca
 	return error("%s: is a directory - add files inside instead", path);
1353ca
 }
1353ca
 
1353ca
-static int process_path(const char *path)
1353ca
+static int process_path(const char *path, struct stat *st, int stat_errno)
1353ca
 {
1353ca
 	int pos, len;
1353ca
-	struct stat st;
1353ca
 	const struct cache_entry *ce;
1353ca
 
1353ca
 	len = strlen(path);
1353ca
@@ -381,13 +380,13 @@ static int process_path(const char *path)
1353ca
 	 * First things first: get the stat information, to decide
1353ca
 	 * what to do about the pathname!
1353ca
 	 */
1353ca
-	if (lstat(path, &st) < 0)
1353ca
-		return process_lstat_error(path, errno);
1353ca
+	if (stat_errno)
1353ca
+		return process_lstat_error(path, stat_errno);
1353ca
 
1353ca
-	if (S_ISDIR(st.st_mode))
1353ca
-		return process_directory(path, len, &st);
1353ca
+	if (S_ISDIR(st->st_mode))
1353ca
+		return process_directory(path, len, st);
1353ca
 
1353ca
-	return add_one_path(ce, path, len, &st);
1353ca
+	return add_one_path(ce, path, len, st);
1353ca
 }
1353ca
 
1353ca
 static int add_cacheinfo(unsigned int mode, const unsigned char *sha1,
1353ca
@@ -451,6 +450,16 @@ static void chmod_path(int flip, const char *path)
1353ca
 
1353ca
 static void update_one(const char *path)
1353ca
 {
1353ca
+	int stat_errno = 0;
1353ca
+	struct stat st;
1353ca
+
1353ca
+	if (mark_valid_only || mark_skip_worktree_only || force_remove)
1353ca
+		st.st_mode = 0;
1353ca
+	else if (lstat(path, &st) < 0) {
1353ca
+		st.st_mode = 0;
1353ca
+		stat_errno = errno;
1353ca
+	} /* else stat is valid */
1353ca
+
1353ca
 	if (!verify_path(path)) {
1353ca
 		fprintf(stderr, "Ignoring path %s\n", path);
1353ca
 		return;
1353ca
@@ -472,7 +481,7 @@ static void update_one(const char *path)
1353ca
 		report("remove '%s'", path);
1353ca
 		return;
1353ca
 	}
1353ca
-	if (process_path(path))
1353ca
+	if (process_path(path, &st, stat_errno))
1353ca
 		die("Unable to process path %s", path);
1353ca
 	report("add '%s'", path);
1353ca
 }
1353ca
-- 
1353ca
2.14.4
1353ca
1353ca
1353ca
From 1d5af177a0853d286069176b11f661ed0e069faa Mon Sep 17 00:00:00 2001
1353ca
From: Jeff King <peff@peff.net>
1353ca
Date: Fri, 4 May 2018 20:03:35 -0400
1353ca
Subject: [PATCH 10/11] verify_path: disallow symlinks in .gitmodules
1353ca
1353ca
There are a few reasons it's not a good idea to make
1353ca
.gitmodules a symlink, including:
1353ca
1353ca
  1. It won't be portable to systems without symlinks.
1353ca
1353ca
  2. It may behave inconsistently, since Git may look at
1353ca
     this file in the index or a tree without bothering to
1353ca
     resolve any symbolic links. We don't do this _yet_, but
1353ca
     the config infrastructure is there and it's planned for
1353ca
     the future.
1353ca
1353ca
With some clever code, we could make (2) work. And some
1353ca
people may not care about (1) if they only work on one
1353ca
platform. But there are a few security reasons to simply
1353ca
disallow it:
1353ca
1353ca
  a. A symlinked .gitmodules file may circumvent any fsck
1353ca
     checks of the content.
1353ca
1353ca
  b. Git may read and write from the on-disk file without
1353ca
     sanity checking the symlink target. So for example, if
1353ca
     you link ".gitmodules" to "../oops" and run "git
1353ca
     submodule add", we'll write to the file "oops" outside
1353ca
     the repository.
1353ca
1353ca
Again, both of those are problems that _could_ be solved
1353ca
with sufficient code, but given the complications in (1) and
1353ca
(2), we're better off just outlawing it explicitly.
1353ca
1353ca
Note the slightly tricky call to verify_path() in
1353ca
update-index's update_one(). There we may not have a mode if
1353ca
we're not updating from the filesystem (e.g., we might just
1353ca
be removing the file). Passing "0" as the mode there works
1353ca
fine; since it's not a symlink, we'll just skip the extra
1353ca
checks.
1353ca
1353ca
Signed-off-by: Jeff King <peff@peff.net>
1353ca
---
1353ca
 builtin/apply.c        |  4 ++--
1353ca
 builtin/update-index.c |  6 +++---
1353ca
 cache.h                |  2 +-
1353ca
 read-cache.c           | 40 +++++++++++++++++++++++++++++++---------
1353ca
 4 files changed, 37 insertions(+), 15 deletions(-)
1353ca
1353ca
diff --git a/builtin/apply.c b/builtin/apply.c
1353ca
index c770d7d..8602b98 100644
1353ca
--- a/builtin/apply.c
1353ca
+++ b/builtin/apply.c
1353ca
@@ -3683,9 +3683,9 @@ static void die_on_unsafe_path(struct patch *patch)
1353ca
 	if (!patch->is_delete)
1353ca
 		new_name = patch->new_name;
1353ca
 
1353ca
-	if (old_name && !verify_path(old_name))
1353ca
+	if (old_name && !verify_path(old_name, patch->old_mode))
1353ca
 		die(_("invalid path '%s'"), old_name);
1353ca
-	if (new_name && !verify_path(new_name))
1353ca
+	if (new_name && !verify_path(new_name, patch->new_mode))
1353ca
 		die(_("invalid path '%s'"), new_name);
1353ca
 }
1353ca
 
1353ca
diff --git a/builtin/update-index.c b/builtin/update-index.c
1353ca
index 0c5be84..c54bada 100644
1353ca
--- a/builtin/update-index.c
1353ca
+++ b/builtin/update-index.c
1353ca
@@ -395,7 +395,7 @@ static int add_cacheinfo(unsigned int mode, const unsigned char *sha1,
1353ca
 	int size, len, option;
1353ca
 	struct cache_entry *ce;
1353ca
 
1353ca
-	if (!verify_path(path))
1353ca
+	if (!verify_path(path, mode))
1353ca
 		return error("Invalid path '%s'", path);
1353ca
 
1353ca
 	len = strlen(path);
1353ca
@@ -460,7 +460,7 @@ static void update_one(const char *path)
1353ca
 		stat_errno = errno;
1353ca
 	} /* else stat is valid */
1353ca
 
1353ca
-	if (!verify_path(path)) {
1353ca
+	if (!verify_path(path, st.st_mode)) {
1353ca
 		fprintf(stderr, "Ignoring path %s\n", path);
1353ca
 		return;
1353ca
 	}
1353ca
@@ -550,7 +550,7 @@ static void read_index_info(int nul_term_line)
1353ca
 			path_name = uq.buf;
1353ca
 		}
1353ca
 
1353ca
-		if (!verify_path(path_name)) {
1353ca
+		if (!verify_path(path_name, mode)) {
1353ca
 			fprintf(stderr, "Ignoring path %s\n", path_name);
1353ca
 			continue;
1353ca
 		}
1353ca
diff --git a/cache.h b/cache.h
1353ca
index fbbe933..61d43c6 100644
1353ca
--- a/cache.h
1353ca
+++ b/cache.h
1353ca
@@ -560,7 +560,7 @@ extern int read_index_unmerged(struct index_state *);
1353ca
 extern int write_locked_index(struct index_state *, struct lock_file *lock, unsigned flags);
1353ca
 extern int discard_index(struct index_state *);
1353ca
 extern int unmerged_index(const struct index_state *);
1353ca
-extern int verify_path(const char *path);
1353ca
+extern int verify_path(const char *path, unsigned mode);
1353ca
 extern int index_dir_exists(struct index_state *istate, const char *name, int namelen);
1353ca
 extern void adjust_dirname_case(struct index_state *istate, char *name);
1353ca
 extern struct cache_entry *index_file_exists(struct index_state *istate, const char *name, int namelen, int igncase);
1353ca
diff --git a/read-cache.c b/read-cache.c
1353ca
index 88ed49c..d6dedd5 100644
1353ca
--- a/read-cache.c
1353ca
+++ b/read-cache.c
1353ca
@@ -738,7 +738,7 @@ struct cache_entry *make_cache_entry(unsigned int mode,
1353ca
 	int size, len;
1353ca
 	struct cache_entry *ce, *ret;
1353ca
 
1353ca
-	if (!verify_path(path)) {
1353ca
+	if (!verify_path(path, mode)) {
1353ca
 		error("Invalid path '%s'", path);
1353ca
 		return NULL;
1353ca
 	}
1353ca
@@ -773,7 +773,7 @@ int ce_same_name(const struct cache_entry *a, const struct cache_entry *b)
1353ca
  * Also, we don't want double slashes or slashes at the
1353ca
  * end that can make pathnames ambiguous.
1353ca
  */
1353ca
-static int verify_dotfile(const char *rest)
1353ca
+static int verify_dotfile(const char *rest, unsigned mode)
1353ca
 {
1353ca
 	/*
1353ca
 	 * The first character was '.', but that
1353ca
@@ -791,6 +791,9 @@ static int verify_dotfile(const char *rest)
1353ca
 	 * case-insensitively here, even if ignore_case is not set.
1353ca
 	 * This outlaws ".GIT" everywhere out of an abundance of caution,
1353ca
 	 * since there's really no good reason to allow it.
1353ca
+	 *
1353ca
+	 * Once we've seen ".git", we can also find ".gitmodules", etc (also
1353ca
+	 * case-insensitively).
1353ca
 	 */
1353ca
 	case 'g':
1353ca
 	case 'G':
1353ca
@@ -800,6 +803,12 @@ static int verify_dotfile(const char *rest)
1353ca
 			break;
1353ca
 		if (rest[3] == '\0' || is_dir_sep(rest[3]))
1353ca
 			return 0;
1353ca
+		if (S_ISLNK(mode)) {
1353ca
+			rest += 3;
1353ca
+			if (skip_iprefix(rest, "modules", &rest) &&
1353ca
+			    (*rest == '\0' || is_dir_sep(*rest)))
1353ca
+				return 0;
1353ca
+		}
1353ca
 		break;
1353ca
 	case '.':
1353ca
 		if (rest[1] == '\0' || is_dir_sep(rest[1]))
1353ca
@@ -808,7 +817,7 @@ static int verify_dotfile(const char *rest)
1353ca
 	return 1;
1353ca
 }
1353ca
 
1353ca
-int verify_path(const char *path)
1353ca
+int verify_path(const char *path, unsigned mode)
1353ca
 {
1353ca
 	char c;
1353ca
 
1353ca
@@ -821,12 +830,25 @@ int verify_path(const char *path)
1353ca
 			return 1;
1353ca
 		if (is_dir_sep(c)) {
1353ca
 inside:
1353ca
-			if (protect_hfs && is_hfs_dotgit(path))
1353ca
-				return 0;
1353ca
-			if (protect_ntfs && is_ntfs_dotgit(path))
1353ca
-				return 0;
1353ca
+			if (protect_hfs) {
1353ca
+				if (is_hfs_dotgit(path))
1353ca
+					return 0;
1353ca
+				if (S_ISLNK(mode)) {
1353ca
+					if (is_hfs_dotgitmodules(path))
1353ca
+						return 0;
1353ca
+				}
1353ca
+			}
1353ca
+			if (protect_ntfs) {
1353ca
+				if (is_ntfs_dotgit(path))
1353ca
+					return 0;
1353ca
+				if (S_ISLNK(mode)) {
1353ca
+					if (is_ntfs_dotgitmodules(path))
1353ca
+						return 0;
1353ca
+				}
1353ca
+			}
1353ca
+
1353ca
 			c = *path++;
1353ca
-			if ((c == '.' && !verify_dotfile(path)) ||
1353ca
+			if ((c == '.' && !verify_dotfile(path, mode)) ||
1353ca
 			    is_dir_sep(c) || c == '\0')
1353ca
 				return 0;
1353ca
 		}
1353ca
@@ -1008,7 +1030,7 @@ static int add_index_entry_with_check(struct index_state *istate, struct cache_e
1353ca
 
1353ca
 	if (!ok_to_add)
1353ca
 		return -1;
1353ca
-	if (!verify_path(ce->name))
1353ca
+	if (!verify_path(ce->name, ce->ce_mode))
1353ca
 		return error("Invalid path '%s'", ce->name);
1353ca
 
1353ca
 	if (!skip_df_check &&
1353ca
-- 
1353ca
2.14.4
1353ca
1353ca
1353ca
From ab1977d8fdcf7b259e1a4ede2f99423dca97ea41 Mon Sep 17 00:00:00 2001
1353ca
From: Sebastian Kisela <skisela@redhat.com>
1353ca
Date: Wed, 20 Jun 2018 15:03:18 +0200
1353ca
Subject: [PATCH 11/11] submodules: make t7415 test accept fail exit code
1353ca
1353ca
The command that performs the cloning fails with non zero exit
1353ca
code, but the exit code itself is not important.
1353ca
The command may fail but the general output must be as expected.
1353ca
---
1353ca
 t/t7415-submodule-names.sh | 2 +-
1353ca
 1 file changed, 1 insertion(+), 1 deletion(-)
1353ca
1353ca
diff --git a/t/t7415-submodule-names.sh b/t/t7415-submodule-names.sh
1353ca
index 75fa071..b30fa33 100755
1353ca
--- a/t/t7415-submodule-names.sh
1353ca
+++ b/t/t7415-submodule-names.sh
1353ca
@@ -69,7 +69,7 @@ test_expect_success 'add other submodule' '
1353ca
 '
1353ca
 
1353ca
 test_expect_success 'clone evil superproject' '
1353ca
-	git clone --recurse-submodules . victim >output 2>&1 &&
1353ca
+	test_might_fail git clone --recurse-submodules . victim >output 2>&1 &&
1353ca
 	! grep "RUNNING POST CHECKOUT" output
1353ca
 '
1353ca
 
1353ca
-- 
1353ca
2.14.4
1353ca