CVE-2020-12278: Use of Incorrectly-Resolved Name or Reference (CWE-706)
value: An issue was discovered in libgit2 before 0.28.4 and 0.9x before 0.99.0. path.c mishandles equivalent filenames that exist because of NTFS Alternate Data Streams. This may allow remote code execution when cloning a repository. This issue is similar to CVE-2019-1352.
CWE description: The product uses a name or reference to access a resource, but the name/reference resolves to a resource that is outside of the intended control sphere.
CVSS v3 base score: 9.8
Published: 2020-04-27T17:15Z
Repository: https://github.com/libgit2/libgit2
Language: C
Files changed: src/path.c, tests/checkout/nasty.c
Commit date: 2019-12-10 18:01:06 +1000
Commit message: Disallow NTFS Alternate Data Stream attacks, even on Linux/macOS

A little-known feature of NTFS is that it offers to store metadata in
so-called "Alternate Data Streams" (inspired by Apple's "resource
forks") that are copied together with the file they are associated with.
These Alternate Data Streams can be accessed via `<file name>:<stream
name>:<stream type>`.

Directories, too, have Alternate Data Streams, and they even have a
default stream type `$INDEX_ALLOCATION`. Which means that `abc/` and
`abc::$INDEX_ALLOCATION/` are actually equivalent.

This is of course another attack vector on the Git directory that we
definitely want to prevent.

On Windows, we already do this incidentally, by disallowing colons in
file/directory names.

While it looks as if files'/directories' Alternate Data Streams are not
accessible in the Windows Subsystem for Linux, and neither via
CIFS/SMB-mounted network shares in Linux, it _is_ possible to access
them on SMB-mounted network shares on macOS.

Therefore, let's go the extra mile and prevent this particular attack
_everywhere_. To keep things simple, let's just disallow *any* Alternate
Data Stream of `.git`.

This is libgit2's variant of CVE-2019-1352.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>

Diff: diff --git a/src/path.c b/src/path.c
index 732834fbef..7844da6722 100644
--- a/src/path.c
+++ b/src/path.c
@@ -1622,12 +1622,16 @@ GIT_INLINE(bool) verify_dotgit_ntfs(git_repository *repo, const char *path, size
 	}
 
 	if (!start)
 		return true;
 
-	/* Reject paths like ".git\" */
-	if (path[start] == '\\')
+	/*
+	 * Reject paths that start with Windows-style directory separators
+	 * (".git\") or NTFS alternate streams (".git:") and could be used
+	 * to write to the ".git" directory on Windows platforms.
+	 */
+	if (path[start] == '\\' || path[start] == ':')
 		return false;
 
 	/* Reject paths like '.git ' or '.git.' */
 	for (i = start; i < len; i++) {
 		if (path[i] != ' ' && path[i] != '.')
diff --git a/tests/checkout/nasty.c b/tests/checkout/nasty.c
index a0ac738a81..611e850d39 100644
--- a/tests/checkout/nasty.c
+++ b/tests/checkout/nasty.c
@@ -271,10 +271,20 @@ void test_checkout_nasty__dot_git_colon_stuff(void)
 #ifdef GIT_WIN32
 	test_checkout_fails("refs/heads/dot_git_colon_stuff", ".git/foobar");
 #endif
 }
 
+/* A tree that contains an entry ".git::$INDEX_ALLOCATION" because NTFS
+ * will interpret that as a synonym to ".git", even when mounted via SMB
+ * on macOS.
+ */
+void test_checkout_nasty__dotgit_alternate_data_stream(void)
+{
+	test_checkout_fails("refs/heads/dotgit_alternate_data_stream", ".git/dummy-file");
+	test_checkout_fails("refs/heads/dotgit_alternate_data_stream", ".git::$INDEX_ALLOCATION/dummy-file");
+}
+
 /* Trees that contains entries with a tree ".git" that contain
  * byte sequences:
  * { 0xe2, 0x80, 0x8c }
  * { 0xe2, 0x80, 0x8d }
  * { 0xe2, 0x80, 0x8e }
diff --git a/tests/resources/nasty/.gitted/objects/33/8190107c7ee7d8f5aa30061fc19b7d5ddcda86 b/tests/resources/nasty/.gitted/objects/33/8190107c7ee7d8f5aa30061fc19b7d5ddcda86
new file mode 100644
index 0000000000..e539ccfec8
Binary files /dev/null and b/tests/resources/nasty/.gitted/objects/33/8190107c7ee7d8f5aa30061fc19b7d5ddcda86 differ
diff --git a/tests/resources/nasty/.gitted/objects/97/c14994866466aeb73e769a6f34e07c7f4b53f7 b/tests/resources/nasty/.gitted/objects/97/c14994866466aeb73e769a6f34e07c7f4b53f7
new file mode 100644
index 0000000000..9f76799173
Binary files /dev/null and b/tests/resources/nasty/.gitted/objects/97/c14994866466aeb73e769a6f34e07c7f4b53f7 differ
diff --git a/tests/resources/nasty/.gitted/objects/b8/edf3ad62dbcbc983857a5bfee7b0181ee1a513 b/tests/resources/nasty/.gitted/objects/b8/edf3ad62dbcbc983857a5bfee7b0181ee1a513
new file mode 100644
index 0000000000..bf446263c7
Binary files /dev/null and b/tests/resources/nasty/.gitted/objects/b8/edf3ad62dbcbc983857a5bfee7b0181ee1a513 differ
diff --git a/tests/resources/nasty/.gitted/refs/heads/dotgit_alternate_data_stream b/tests/resources/nasty/.gitted/refs/heads/dotgit_alternate_data_stream
new file mode 100644
index 0000000000..ecdd340cd4
Binary files /dev/null and b/tests/resources/nasty/.gitted/refs/heads/dotgit_alternate_data_stream differ

Vulnerable code: 	/* Reject paths like ".git\" */
	if (path[start] == '\\')
Fixed code: 	/*
	 * Reject paths that start with Windows-style directory separators
	 * (".git\") or NTFS alternate streams (".git:") and could be used
	 * to write to the ".git" directory on Windows platforms.
	 */
	if (path[start] == '\\' || path[start] == ':')
/* A tree that contains an entry ".git::$INDEX_ALLOCATION" because NTFS
 * will interpret that as a synonym to ".git", even when mounted via SMB
 * on macOS.
 */
void test_checkout_nasty__dotgit_alternate_data_stream(void)
{
	test_checkout_fails("refs/heads/dotgit_alternate_data_stream", ".git/dummy-file");
	test_checkout_fails("refs/heads/dotgit_alternate_data_stream", ".git::$INDEX_ALLOCATION/dummy-file");
}