| /* |
| * 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 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()); |
| 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], mode, insertId); |
| 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")); |
| } |
| } |
| } |
| |
| } |