blob: 057e0c881bf5f9fc08d37cb3b387ea3c967043ef [file] [log] [blame]
/*
* Copyright (C) 2011, Robin Rosenberg <robin.rosenberg@dewire.com>
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v1.0 which accompanies this
* distribution, is reproduced below, and is available at
* http://www.eclipse.org/org/documents/edl-v10.php
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* - Neither the name of the Eclipse Foundation, Inc. nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package org.eclipse.jgit.lib;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.dircache.InvalidPathException;
import org.eclipse.jgit.junit.MockSystemReader;
import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.util.SystemReader;
import org.junit.Test;
public class DirCacheCheckoutMaliciousPathTest extends RepositoryTestCase {
protected ObjectId theHead;
protected ObjectId theMerge;
@Test
public void testMaliciousAbsolutePathIsOk() throws Exception {
testMaliciousPathGoodFirstCheckout("ok");
}
@Test
public void testMaliciousAbsolutePathIsOkSecondCheckout() throws Exception {
testMaliciousPathGoodSecondCheckout("ok");
}
@Test
public void testMaliciousAbsolutePathIsOkTwoLevels() throws Exception {
testMaliciousPathGoodSecondCheckout("a", "ok");
}
@Test
public void testMaliciousAbsolutePath() throws Exception {
testMaliciousPathBadFirstCheckout("/tmp/x");
}
@Test
public void testMaliciousAbsolutePathSecondCheckout() throws Exception {
testMaliciousPathBadSecondCheckout("/tmp/x");
}
@Test
public void testMaliciousAbsolutePathTwoLevelsFirstBad() throws Exception {
testMaliciousPathBadFirstCheckout("/tmp/x", "y");
}
@Test
public void testMaliciousAbsolutePathTwoLevelsSecondBad() throws Exception {
testMaliciousPathBadFirstCheckout("y", "/tmp/x");
}
@Test
public void testMaliciousAbsoluteCurDrivePathWindows() throws Exception {
((MockSystemReader) SystemReader.getInstance()).setWindows();
testMaliciousPathBadFirstCheckout("\\somepath");
}
@Test
public void testMaliciousAbsoluteCurDrivePathWindowsOnUnix()
throws Exception {
((MockSystemReader) SystemReader.getInstance()).setUnix();
testMaliciousPathGoodFirstCheckout("\\somepath");
}
@Test
public void testMaliciousAbsoluteUNCPathWindows1() throws Exception {
((MockSystemReader) SystemReader.getInstance()).setWindows();
testMaliciousPathBadFirstCheckout("\\\\somepath");
}
@Test
public void testMaliciousAbsoluteUNCPathWindows1OnUnix() throws Exception {
((MockSystemReader) SystemReader.getInstance()).setUnix();
testMaliciousPathGoodFirstCheckout("\\\\somepath");
}
@Test
public void testMaliciousAbsoluteUNCPathWindows2() throws Exception {
((MockSystemReader) SystemReader.getInstance()).setWindows();
testMaliciousPathBadFirstCheckout("\\/somepath");
}
@Test
public void testMaliciousAbsoluteUNCPathWindows2OnUnix() throws Exception {
((MockSystemReader) SystemReader.getInstance()).setUnix();
testMaliciousPathBadFirstCheckout("\\/somepath");
}
@Test
public void testMaliciousAbsoluteWindowsPath1() throws Exception {
((MockSystemReader) SystemReader.getInstance()).setWindows();
testMaliciousPathBadFirstCheckout("c:\\temp\\x");
}
@Test
public void testMaliciousAbsoluteWindowsPath1OnUnix() throws Exception {
if (File.separatorChar == '\\')
return; // cannot emulate Unix on Windows for this test
((MockSystemReader) SystemReader.getInstance()).setUnix();
testMaliciousPathGoodFirstCheckout("c:\\temp\\x");
}
@Test
public void testMaliciousAbsoluteWindowsPath2() throws Exception {
((MockSystemReader) SystemReader.getInstance()).setCurrentPlatform();
testMaliciousPathBadFirstCheckout("c:/temp/x");
}
@Test
public void testMaliciousGitPath1() throws Exception {
testMaliciousPathBadFirstCheckout(".git/konfig");
}
@Test
public void testMaliciousGitPath2() throws Exception {
testMaliciousPathBadFirstCheckout(".git", "konfig");
}
@Test
public void testMaliciousGitPath1Case() throws Exception {
((MockSystemReader) SystemReader.getInstance()).setWindows(); // or OS X
testMaliciousPathBadFirstCheckout(".Git/konfig");
}
@Test
public void testMaliciousGitPath2Case() throws Exception {
((MockSystemReader) SystemReader.getInstance()).setWindows(); // or OS X
testMaliciousPathBadFirstCheckout(".gIt", "konfig");
}
@Test
public void testMaliciousGitPath3Case() throws Exception {
((MockSystemReader) SystemReader.getInstance()).setWindows(); // or OS X
testMaliciousPathBadFirstCheckout(".giT", "konfig");
}
@Test
public void testMaliciousGitPathEndSpaceWindows() throws Exception {
((MockSystemReader) SystemReader.getInstance()).setWindows();
testMaliciousPathBadFirstCheckout(".git ", "konfig");
}
@Test
public void testMaliciousGitPathEndSpaceUnixOk() throws Exception {
testMaliciousPathBadFirstCheckout(".git ", "konfig");
}
@Test
public void testMaliciousGitPathEndDotWindows1() throws Exception {
((MockSystemReader) SystemReader.getInstance()).setWindows();
testMaliciousPathBadFirstCheckout(".git.", "konfig");
}
@Test
public void testMaliciousGitPathEndDotWindows2() throws Exception {
((MockSystemReader) SystemReader.getInstance()).setWindows();
testMaliciousPathBadFirstCheckout(".f.");
}
@Test
public void testMaliciousGitPathEndDotWindows3() throws Exception {
((MockSystemReader) SystemReader.getInstance()).setWindows();
testMaliciousPathGoodFirstCheckout(".f");
}
@Test
public void testMaliciousGitPathEndDotUnixOk() throws Exception {
testMaliciousPathBadFirstCheckout(".git.", "konfig");
}
@Test
public void testMaliciousPathDotDot() throws Exception {
((MockSystemReader) SystemReader.getInstance()).setCurrentPlatform();
testMaliciousPathBadFirstCheckout("..", "no");
}
@Test
public void testMaliciousPathDot() throws Exception {
((MockSystemReader) SystemReader.getInstance()).setCurrentPlatform();
testMaliciousPathBadFirstCheckout(".", "no");
}
@Test
public void testMaliciousPathEmptyUnix() throws Exception {
((MockSystemReader) SystemReader.getInstance()).setUnix();
testMaliciousPathBadFirstCheckout("", "no");
}
@Test
public void testMaliciousPathEmptyWindows() throws Exception {
((MockSystemReader) SystemReader.getInstance()).setWindows();
testMaliciousPathBadFirstCheckout("", "no");
}
@Test
public void testMaliciousWindowsADS() throws Exception {
((MockSystemReader) SystemReader.getInstance()).setWindows();
testMaliciousPathBadFirstCheckout("some:path");
}
@Test
public void testMaliciousWindowsADSOnUnix() throws Exception {
if (File.separatorChar == '\\')
return; // cannot emulate Unix on Windows for this test
((MockSystemReader) SystemReader.getInstance()).setUnix();
testMaliciousPathGoodFirstCheckout("some:path");
}
@Test
public void testForbiddenNamesOnWindowsEgCon() throws Exception {
((MockSystemReader) SystemReader.getInstance()).setWindows();
testMaliciousPathBadFirstCheckout("con");
}
@Test
public void testForbiddenNamesOnWindowsEgConDotSuffix() throws Exception {
((MockSystemReader) SystemReader.getInstance()).setWindows();
testMaliciousPathBadFirstCheckout("con.txt");
}
@Test
public void testForbiddenNamesOnWindowsEgLpt1() throws Exception {
((MockSystemReader) SystemReader.getInstance()).setWindows();
testMaliciousPathBadFirstCheckout("lpt1");
}
@Test
public void testForbiddenNamesOnWindowsEgLpt1DotSuffix() throws Exception {
((MockSystemReader) SystemReader.getInstance()).setWindows();
testMaliciousPathBadFirstCheckout("lpt1.txt");
}
@Test
public void testForbiddenNamesOnWindowsEgDotCon() throws Exception {
((MockSystemReader) SystemReader.getInstance()).setWindows();
testMaliciousPathGoodFirstCheckout(".con");
}
@Test
public void testForbiddenNamesOnWindowsEgLpr() throws Exception {
((MockSystemReader) SystemReader.getInstance()).setWindows();
testMaliciousPathGoodFirstCheckout("lpt"); // good name
}
@Test
public void testForbiddenNamesOnWindowsEgCon1() throws Exception {
((MockSystemReader) SystemReader.getInstance()).setWindows();
testMaliciousPathGoodFirstCheckout("con1"); // good name
}
@Test
public void testForbiddenWindowsNamesOnUnixEgCon() throws Exception {
if (File.separatorChar == '\\')
return; // cannot emulate Unix on Windows for this test
testMaliciousPathGoodFirstCheckout("con");
}
@Test
public void testForbiddenWindowsNamesOnUnixEgLpt1() throws Exception {
if (File.separatorChar == '\\')
return; // cannot emulate Unix on Windows for this test
testMaliciousPathGoodFirstCheckout("lpt1");
}
private void testMaliciousPathBadFirstCheckout(String... paths)
throws Exception {
testMaliciousPath(false, false, paths);
}
private void testMaliciousPathBadSecondCheckout(String... paths) throws Exception {
testMaliciousPath(false, true, paths);
}
private void testMaliciousPathGoodFirstCheckout(String... paths)
throws Exception {
testMaliciousPath(true, false, paths);
}
private void testMaliciousPathGoodSecondCheckout(String... paths) throws Exception {
testMaliciousPath(true, true, paths);
}
/**
* Create a bad tree and tries to check it out
*
* @param good
* true if we expect this to pass
* @param secondCheckout
* perform the actual test on the second checkout
* @param path
* to the blob, one or more levels
* @throws GitAPIException
* @throws IOException
*/
private void testMaliciousPath(boolean good, boolean secondCheckout,
String... path) throws GitAPIException, IOException {
try (Git git = new Git(db);
RevWalk revWalk = new RevWalk(git.getRepository())) {
ObjectInserter newObjectInserter;
newObjectInserter = git.getRepository().newObjectInserter();
ObjectId blobId = newObjectInserter.insert(Constants.OBJ_BLOB,
"data".getBytes(UTF_8));
newObjectInserter = git.getRepository().newObjectInserter();
FileMode mode = FileMode.REGULAR_FILE;
ObjectId insertId = blobId;
for (int i = path.length - 1; i >= 0; --i) {
TreeFormatter treeFormatter = new TreeFormatter();
treeFormatter.append("goodpath", mode, insertId);
insertId = newObjectInserter.insert(treeFormatter);
mode = FileMode.TREE;
}
newObjectInserter = git.getRepository().newObjectInserter();
CommitBuilder commitBuilder = new CommitBuilder();
commitBuilder.setAuthor(author);
commitBuilder.setCommitter(committer);
commitBuilder.setMessage("foo#1");
commitBuilder.setTreeId(insertId);
ObjectId firstCommitId = newObjectInserter.insert(commitBuilder);
newObjectInserter = git.getRepository().newObjectInserter();
mode = FileMode.REGULAR_FILE;
insertId = blobId;
for (int i = path.length - 1; i >= 0; --i) {
TreeFormatter treeFormatter = new TreeFormatter();
treeFormatter.append(path[i].getBytes(UTF_8), 0,
path[i].getBytes(UTF_8).length,
mode, insertId, true);
insertId = newObjectInserter.insert(treeFormatter);
mode = FileMode.TREE;
}
// Create another commit
commitBuilder = new CommitBuilder();
commitBuilder.setAuthor(author);
commitBuilder.setCommitter(committer);
commitBuilder.setMessage("foo#2");
commitBuilder.setTreeId(insertId);
commitBuilder.setParentId(firstCommitId);
ObjectId commitId = newObjectInserter.insert(commitBuilder);
if (!secondCheckout)
git.checkout().setStartPoint(revWalk.parseCommit(firstCommitId))
.setName("refs/heads/master").setCreateBranch(true).call();
try {
if (secondCheckout) {
git.checkout().setStartPoint(revWalk.parseCommit(commitId))
.setName("refs/heads/master").setCreateBranch(true)
.call();
} else {
git.branchCreate().setName("refs/heads/next")
.setStartPoint(commitId.name()).call();
git.checkout().setName("refs/heads/next")
.call();
}
if (!good)
fail("Checkout of Tree " + Arrays.asList(path) + " should fail");
} catch (InvalidPathException e) {
if (good)
throw e;
assertTrue(e.getMessage().startsWith("Invalid path"));
}
}
}
}