[PATCH 6/7] verify_path(): disallow symlinks in .gitattributes and .gitignore

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



In commit 10ecfa7649 (verify_path: disallow symlinks in .gitmodules,
2018-05-04) we made it impossible to load a .gitmodules file that's a
symlink into the index. The security reasons for doing so are described
there. We also discussed forbidding symlinks of other .git files as part
of that fix, but the tradeoff was less compelling:

  1. Unlike .gitmodules, the other files don't have content-level fsck
     checks. So an attacker using symlinks to evade those checks isn't a
     problem.

  2. Unlike .gitmodules, Git will never write .gitignore or
     .gitattributes itself, making it much less likely to use them to
     write outside the repo. They could be used for out-of-repo reads,
     however.

  3. The .gitmodules change was part of a critical bug-fix that was
     not publicly disclosed until it was released. Changing the other
     files was not needed for the minimal fix.

However, it's still a reasonable idea to forbid symlinks for these
files:

  - As noted, they can still be used to read out-of-repo files (which is
    fairly restricted, but in some circumstances you can probe file
    content by speculatively creating files and seeing if they get
    ignored)

  - They don't currently behave well in all cases. We sometimes read
    these files from the index, where we _don't_ follow symlinks (we'd
    just treat the symlink target as the .gitignore or .gitattributes
    content, which is actively wrong).

This patch forbids symlinked versions of these files from entering the
index. We already have helpers for obscured forms of the names from
e7cb0b4455 (is_ntfs_dotgit: match other .git files, 2018-05-11) and
0fc333ba20 (is_hfs_dotgit: match other .git files, 2018-05-02), which
were done as part of the series touching .gitmodules.

Signed-off-by: Jeff King <peff@xxxxxxxx>
---
I note that neither these new tests nor the existing .gitmodules ones
confirm that we catch the obscured ntfs/hfs forms in the actual code
paths (instead, we feed them to a synthetic test-tool helper in t0060).
I think that's OK, but if we wanted to be super-paranoid we could beef
up these tests with trickier names.

 read-cache.c              | 12 +++++++++---
 t/t7450-bad-meta-files.sh | 29 +++++++++++++++++++++++++++++
 2 files changed, 38 insertions(+), 3 deletions(-)

diff --git a/read-cache.c b/read-cache.c
index ecf6f68994..63aec6c35d 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -947,7 +947,9 @@ static int verify_dotfile(const char *rest, unsigned mode)
 			return 0;
 		if (S_ISLNK(mode)) {
 			rest += 3;
-			if (skip_iprefix(rest, "modules", &rest) &&
+			if ((skip_iprefix(rest, "modules", &rest) ||
+			     skip_iprefix(rest, "ignore", &rest) ||
+			     skip_iprefix(rest, "attributes", &rest)) &&
 			    (*rest == '\0' || is_dir_sep(*rest)))
 				return 0;
 		}
@@ -980,7 +982,9 @@ int verify_path(const char *path, unsigned mode)
 				if (is_hfs_dotgit(path))
 					return 0;
 				if (S_ISLNK(mode)) {
-					if (is_hfs_dotgitmodules(path))
+					if (is_hfs_dotgitmodules(path) ||
+					    is_hfs_dotgitignore(path) ||
+					    is_hfs_dotgitattributes(path))
 						return 0;
 				}
 			}
@@ -992,7 +996,9 @@ int verify_path(const char *path, unsigned mode)
 				if (is_ntfs_dotgit(path))
 					return 0;
 				if (S_ISLNK(mode)) {
-					if (is_ntfs_dotgitmodules(path))
+					if (is_ntfs_dotgitmodules(path) ||
+					    is_ntfs_dotgitignore(path) ||
+					    is_ntfs_dotgitattributes(path))
 						return 0;
 				}
 			}
diff --git a/t/t7450-bad-meta-files.sh b/t/t7450-bad-meta-files.sh
index b73985157f..6a038ed55b 100755
--- a/t/t7450-bad-meta-files.sh
+++ b/t/t7450-bad-meta-files.sh
@@ -267,4 +267,33 @@ test_expect_success 'git dirs of sibling submodules must not be nested' '
 	test_i18ngrep "is inside git dir" err
 '
 
+test_expect_success 'create repo with symlinked .gitattributes file' '
+	git init symlink-attr &&
+	target=$(echo target | git -C symlink-attr hash-object -w --stdin) &&
+	tree=$(
+		printf "120000 blob $target\t.gitattributes\n" |
+		git -C symlink-attr mktree
+	)
+'
+
+test_expect_success 'refuse to load symlinked .gitattributes into index' '
+	test_must_fail git -C symlink-attr read-tree $tree 2>err &&
+	test_i18ngrep "invalid path.*gitattributes" err
+'
+
+test_expect_success 'create repo with symlinked .gitignore file' '
+	git init symlink-ignore &&
+	target=$(echo target | git -C symlink-ignore hash-object -w --stdin) &&
+	tree=$(
+		printf "120000 blob $target\t.gitignore\n" |
+		git -C symlink-ignore mktree
+	)
+'
+
+test_expect_success 'refuse to load symlinked .gitignore into index' '
+	test_must_fail git -C symlink-ignore read-tree $tree 2>err &&
+	test_i18ngrep "invalid path.*gitignore" err
+'
+
+
 test_done
-- 
2.28.0.1295.g4824feede7




[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]

  Powered by Linux