ObjectChecker: Disallow ".git." and ".git<space>" Windows treats "foo." and "foo " as "foo". The ".git" directory is special, as it contains metadata for a local Git repository. Disallow variations that Windows considers to be the same. Change-Id: I28eb48859a95a89111b4987c91de97557e3bb539
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutMaliciousPathTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutMaliciousPathTest.java index d29a75e..ca3e066 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutMaliciousPathTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutMaliciousPathTest.java
@@ -186,10 +186,7 @@ public void testMaliciousGitPathEndSpaceWindows() throws Exception { @Test public void testMaliciousGitPathEndSpaceUnixOk() throws Exception { - if (File.separatorChar == '\\') - return; // cannot emulate Unix on Windows for this test - ((MockSystemReader) SystemReader.getInstance()).setUnix(); - testMaliciousPathGoodFirstCheckout(".git ", "konfig"); + testMaliciousPathBadFirstCheckout(".git ", "konfig"); } @Test @@ -212,10 +209,7 @@ public void testMaliciousGitPathEndDotWindows3() throws Exception { @Test public void testMaliciousGitPathEndDotUnixOk() throws Exception { - if (File.separatorChar == '\\') - return; // cannot emulate Unix on Windows for this test - ((MockSystemReader) SystemReader.getInstance()).setUnix(); - testMaliciousPathGoodFirstCheckout(".git.", "konfig"); + testMaliciousPathBadFirstCheckout(".git.", "konfig"); } @Test
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java index f3db2f6..aa17ac3 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java
@@ -1308,6 +1308,102 @@ public void testInvalidTreeNameIsMixedCaseGit() { } @Test + public void testInvalidTreeNameIsDotGitDot() { + StringBuilder b = new StringBuilder(); + entry(b, "100644 .git."); + byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkTree(data); + fail("incorrectly accepted an invalid tree"); + } catch (CorruptObjectException e) { + assertEquals("invalid name '.git.'", e.getMessage()); + } + } + + @Test + public void testValidTreeNameIsDotGitDotDot() + throws CorruptObjectException { + StringBuilder b = new StringBuilder(); + entry(b, "100644 .git.."); + checker.checkTree(Constants.encodeASCII(b.toString())); + } + + @Test + public void testInvalidTreeNameIsDotGitSpace() { + StringBuilder b = new StringBuilder(); + entry(b, "100644 .git "); + byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkTree(data); + fail("incorrectly accepted an invalid tree"); + } catch (CorruptObjectException e) { + assertEquals("invalid name '.git '", e.getMessage()); + } + } + + @Test + public void testInvalidTreeNameIsDotGitSomething() + throws CorruptObjectException { + StringBuilder b = new StringBuilder(); + entry(b, "100644 .gitfoobar"); + byte[] data = Constants.encodeASCII(b.toString()); + checker.checkTree(data); + } + + @Test + public void testInvalidTreeNameIsDotGitSomethingSpaceSomething() + throws CorruptObjectException { + StringBuilder b = new StringBuilder(); + entry(b, "100644 .gitfoo bar"); + byte[] data = Constants.encodeASCII(b.toString()); + checker.checkTree(data); + } + + @Test + public void testInvalidTreeNameIsDotGitSomethingDot() + throws CorruptObjectException { + StringBuilder b = new StringBuilder(); + entry(b, "100644 .gitfoobar."); + byte[] data = Constants.encodeASCII(b.toString()); + checker.checkTree(data); + } + + @Test + public void testInvalidTreeNameIsDotGitSomethingDotDot() + throws CorruptObjectException { + StringBuilder b = new StringBuilder(); + entry(b, "100644 .gitfoobar.."); + byte[] data = Constants.encodeASCII(b.toString()); + checker.checkTree(data); + } + + @Test + public void testInvalidTreeNameIsDotGitDotSpace() { + StringBuilder b = new StringBuilder(); + entry(b, "100644 .git. "); + byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkTree(data); + fail("incorrectly accepted an invalid tree"); + } catch (CorruptObjectException e) { + assertEquals("invalid name '.git. '", e.getMessage()); + } + } + + @Test + public void testInvalidTreeNameIsDotGitSpaceDot() { + StringBuilder b = new StringBuilder(); + entry(b, "100644 .git . "); + byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkTree(data); + fail("incorrectly accepted an invalid tree"); + } catch (CorruptObjectException e) { + assertEquals("invalid name '.git . '", e.getMessage()); + } + } + + @Test public void testInvalidTreeTruncatedInName() { final StringBuilder b = new StringBuilder(); b.append("100644 b");
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java index 92b4e39..1e4163e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java
@@ -453,7 +453,13 @@ private void checkPathSegment2(byte[] raw, int ptr, int end) throw new CorruptObjectException("invalid name '..'"); break; case 4: - if (isDotGit(raw, ptr + 1)) + if (isGit(raw, ptr + 1)) + throw new CorruptObjectException(String.format( + "invalid name '%s'", + RawParseUtils.decode(raw, ptr, end))); + break; + default: + if (end - ptr > 4 && isNormalizedGit(raw, ptr + 1, end)) throw new CorruptObjectException(String.format( "invalid name '%s'", RawParseUtils.decode(raw, ptr, end))); @@ -540,12 +546,30 @@ private static boolean isInvalidOnWindows(byte c) { return 1 <= c && c <= 31; } - private static boolean isDotGit(byte[] buf, int p) { + private static boolean isGit(byte[] buf, int p) { return toLower(buf[p]) == 'g' && toLower(buf[p + 1]) == 'i' && toLower(buf[p + 2]) == 't'; } + private static boolean isNormalizedGit(byte[] raw, int ptr, int end) { + if (isGit(raw, ptr)) { + int dots = 0; + boolean space = false; + int p = end - 1; + for (; (ptr + 2) < p; p--) { + if (raw[p] == '.') + dots++; + else if (raw[p] == ' ') + space = true; + else + break; + } + return p == ptr + 2 && (dots == 1 || space); + } + return false; + } + private static char toLower(byte b) { if ('A' <= b && b <= 'Z') return (char) (b + ('a' - 'A'));