View Javadoc
1   /*
2    * Copyright (C) 2011, Robin Rosenberg <robin.rosenberg@dewire.com>
3    * and other copyright owners as documented in the project's IP log.
4    *
5    * This program and the accompanying materials are made available under the
6    * terms of the Eclipse Distribution License v1.0 which accompanies this
7    * distribution, is reproduced below, and is available at
8    * http://www.eclipse.org/org/documents/edl-v10.php
9    *
10   * All rights reserved.
11   *
12   * Redistribution and use in source and binary forms, with or without
13   * modification, are permitted provided that the following conditions are met:
14   *
15   * - Redistributions of source code must retain the above copyright notice, this
16   * list of conditions and the following disclaimer.
17   *
18   * - Redistributions in binary form must reproduce the above copyright notice,
19   * this list of conditions and the following disclaimer in the documentation
20   * and/or other materials provided with the distribution.
21   *
22   * - Neither the name of the Eclipse Foundation, Inc. nor the names of its
23   * contributors may be used to endorse or promote products derived from this
24   * software without specific prior written permission.
25   *
26   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
27   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
30   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
31   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
32   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
33   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
34   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36   * POSSIBILITY OF SUCH DAMAGE.
37   */
38  package org.eclipse.jgit.lib;
39  
40  import static org.junit.Assert.assertTrue;
41  import static org.junit.Assert.fail;
42  
43  import java.io.File;
44  import java.io.IOException;
45  import java.util.Arrays;
46  
47  import org.eclipse.jgit.api.Git;
48  import org.eclipse.jgit.api.errors.GitAPIException;
49  import org.eclipse.jgit.dircache.InvalidPathException;
50  import org.eclipse.jgit.junit.MockSystemReader;
51  import org.eclipse.jgit.junit.RepositoryTestCase;
52  import org.eclipse.jgit.revwalk.RevWalk;
53  import org.eclipse.jgit.util.SystemReader;
54  import org.junit.Test;
55  
56  public class DirCacheCheckoutMaliciousPathTest extends RepositoryTestCase {
57  
58  	protected ObjectId theHead;
59  	protected ObjectId theMerge;
60  
61  	@Test
62  	public void testMaliciousAbsolutePathIsOk() throws Exception {
63  		testMaliciousPathGoodFirstCheckout("ok");
64  	}
65  
66  	@Test
67  	public void testMaliciousAbsolutePathIsOkSecondCheckout() throws Exception {
68  		testMaliciousPathGoodSecondCheckout("ok");
69  	}
70  
71  	@Test
72  	public void testMaliciousAbsolutePathIsOkTwoLevels() throws Exception {
73  		testMaliciousPathGoodSecondCheckout("a", "ok");
74  	}
75  
76  	@Test
77  	public void testMaliciousAbsolutePath() throws Exception {
78  		testMaliciousPathBadFirstCheckout("/tmp/x");
79  	}
80  
81  	@Test
82  	public void testMaliciousAbsolutePathSecondCheckout() throws Exception {
83  		testMaliciousPathBadSecondCheckout("/tmp/x");
84  	}
85  
86  	@Test
87  	public void testMaliciousAbsolutePathTwoLevelsFirstBad() throws Exception {
88  		testMaliciousPathBadFirstCheckout("/tmp/x", "y");
89  	}
90  
91  	@Test
92  	public void testMaliciousAbsolutePathTwoLevelsSecondBad() throws Exception {
93  		testMaliciousPathBadFirstCheckout("y", "/tmp/x");
94  	}
95  
96  	@Test
97  	public void testMaliciousAbsoluteCurDrivePathWindows() throws Exception {
98  		((MockSystemReader) SystemReader.getInstance()).setWindows();
99  		testMaliciousPathBadFirstCheckout("\\somepath");
100 	}
101 
102 	@Test
103 	public void testMaliciousAbsoluteCurDrivePathWindowsOnUnix()
104 			throws Exception {
105 		((MockSystemReader) SystemReader.getInstance()).setUnix();
106 		testMaliciousPathGoodFirstCheckout("\\somepath");
107 	}
108 
109 	@Test
110 	public void testMaliciousAbsoluteUNCPathWindows1() throws Exception {
111 		((MockSystemReader) SystemReader.getInstance()).setWindows();
112 		testMaliciousPathBadFirstCheckout("\\\\somepath");
113 	}
114 
115 	@Test
116 	public void testMaliciousAbsoluteUNCPathWindows1OnUnix() throws Exception {
117 		((MockSystemReader) SystemReader.getInstance()).setUnix();
118 		testMaliciousPathGoodFirstCheckout("\\\\somepath");
119 	}
120 
121 	@Test
122 	public void testMaliciousAbsoluteUNCPathWindows2() throws Exception {
123 		((MockSystemReader) SystemReader.getInstance()).setWindows();
124 		testMaliciousPathBadFirstCheckout("\\/somepath");
125 	}
126 
127 	@Test
128 	public void testMaliciousAbsoluteUNCPathWindows2OnUnix() throws Exception {
129 		((MockSystemReader) SystemReader.getInstance()).setUnix();
130 		testMaliciousPathBadFirstCheckout("\\/somepath");
131 	}
132 
133 	@Test
134 	public void testMaliciousAbsoluteWindowsPath1() throws Exception {
135 		((MockSystemReader) SystemReader.getInstance()).setWindows();
136 		testMaliciousPathBadFirstCheckout("c:\\temp\\x");
137 	}
138 
139 	@Test
140 	public void testMaliciousAbsoluteWindowsPath1OnUnix() throws Exception {
141 		if (File.separatorChar == '\\')
142 			return; // cannot emulate Unix on Windows for this test
143 		((MockSystemReader) SystemReader.getInstance()).setUnix();
144 		testMaliciousPathGoodFirstCheckout("c:\\temp\\x");
145 	}
146 
147 	@Test
148 	public void testMaliciousAbsoluteWindowsPath2() throws Exception {
149 		((MockSystemReader) SystemReader.getInstance()).setCurrentPlatform();
150 		testMaliciousPathBadFirstCheckout("c:/temp/x");
151 	}
152 
153 	@Test
154 	public void testMaliciousGitPath1() throws Exception {
155 		testMaliciousPathBadFirstCheckout(".git/konfig");
156 	}
157 
158 	@Test
159 	public void testMaliciousGitPath2() throws Exception {
160 		testMaliciousPathBadFirstCheckout(".git", "konfig");
161 	}
162 
163 	@Test
164 	public void testMaliciousGitPath1Case() throws Exception {
165 		((MockSystemReader) SystemReader.getInstance()).setWindows(); // or OS X
166 		testMaliciousPathBadFirstCheckout(".Git/konfig");
167 	}
168 
169 	@Test
170 	public void testMaliciousGitPath2Case() throws Exception {
171 		((MockSystemReader) SystemReader.getInstance()).setWindows(); // or OS X
172 		testMaliciousPathBadFirstCheckout(".gIt", "konfig");
173 	}
174 
175 	@Test
176 	public void testMaliciousGitPath3Case() throws Exception {
177 		((MockSystemReader) SystemReader.getInstance()).setWindows(); // or OS X
178 		testMaliciousPathBadFirstCheckout(".giT", "konfig");
179 	}
180 
181 	@Test
182 	public void testMaliciousGitPathEndSpaceWindows() throws Exception {
183 		((MockSystemReader) SystemReader.getInstance()).setWindows();
184 		testMaliciousPathBadFirstCheckout(".git ", "konfig");
185 	}
186 
187 	@Test
188 	public void testMaliciousGitPathEndSpaceUnixOk() throws Exception {
189 		testMaliciousPathBadFirstCheckout(".git ", "konfig");
190 	}
191 
192 	@Test
193 	public void testMaliciousGitPathEndDotWindows1() throws Exception {
194 		((MockSystemReader) SystemReader.getInstance()).setWindows();
195 		testMaliciousPathBadFirstCheckout(".git.", "konfig");
196 	}
197 
198 	@Test
199 	public void testMaliciousGitPathEndDotWindows2() throws Exception {
200 		((MockSystemReader) SystemReader.getInstance()).setWindows();
201 		testMaliciousPathBadFirstCheckout(".f.");
202 	}
203 
204 	@Test
205 	public void testMaliciousGitPathEndDotWindows3() throws Exception {
206 		((MockSystemReader) SystemReader.getInstance()).setWindows();
207 		testMaliciousPathGoodFirstCheckout(".f");
208 	}
209 
210 	@Test
211 	public void testMaliciousGitPathEndDotUnixOk() throws Exception {
212 		testMaliciousPathBadFirstCheckout(".git.", "konfig");
213 	}
214 
215 	@Test
216 	public void testMaliciousPathDotDot() throws Exception {
217 		((MockSystemReader) SystemReader.getInstance()).setCurrentPlatform();
218 		testMaliciousPathBadFirstCheckout("..", "no");
219 	}
220 
221 	@Test
222 	public void testMaliciousPathDot() throws Exception {
223 		((MockSystemReader) SystemReader.getInstance()).setCurrentPlatform();
224 		testMaliciousPathBadFirstCheckout(".", "no");
225 	}
226 
227 	@Test
228 	public void testMaliciousPathEmptyUnix() throws Exception {
229 		((MockSystemReader) SystemReader.getInstance()).setUnix();
230 		testMaliciousPathBadFirstCheckout("", "no");
231 	}
232 
233 	@Test
234 	public void testMaliciousPathEmptyWindows() throws Exception {
235 		((MockSystemReader) SystemReader.getInstance()).setWindows();
236 		testMaliciousPathBadFirstCheckout("", "no");
237 	}
238 
239 	@Test
240 	public void testMaliciousWindowsADS() throws Exception {
241 		((MockSystemReader) SystemReader.getInstance()).setWindows();
242 		testMaliciousPathBadFirstCheckout("some:path");
243 	}
244 
245 	@Test
246 	public void testMaliciousWindowsADSOnUnix() throws Exception {
247 		if (File.separatorChar == '\\')
248 			return; // cannot emulate Unix on Windows for this test
249 		((MockSystemReader) SystemReader.getInstance()).setUnix();
250 		testMaliciousPathGoodFirstCheckout("some:path");
251 	}
252 
253 	@Test
254 	public void testForbiddenNamesOnWindowsEgCon() throws Exception {
255 		((MockSystemReader) SystemReader.getInstance()).setWindows();
256 		testMaliciousPathBadFirstCheckout("con");
257 	}
258 
259 	@Test
260 	public void testForbiddenNamesOnWindowsEgConDotSuffix() throws Exception {
261 		((MockSystemReader) SystemReader.getInstance()).setWindows();
262 		testMaliciousPathBadFirstCheckout("con.txt");
263 	}
264 
265 	@Test
266 	public void testForbiddenNamesOnWindowsEgLpt1() throws Exception {
267 		((MockSystemReader) SystemReader.getInstance()).setWindows();
268 		testMaliciousPathBadFirstCheckout("lpt1");
269 	}
270 
271 	@Test
272 	public void testForbiddenNamesOnWindowsEgLpt1DotSuffix() throws Exception {
273 		((MockSystemReader) SystemReader.getInstance()).setWindows();
274 		testMaliciousPathBadFirstCheckout("lpt1.txt");
275 	}
276 
277 	@Test
278 	public void testForbiddenNamesOnWindowsEgDotCon() throws Exception {
279 		((MockSystemReader) SystemReader.getInstance()).setWindows();
280 		testMaliciousPathGoodFirstCheckout(".con");
281 	}
282 
283 	@Test
284 	public void testForbiddenNamesOnWindowsEgLpr() throws Exception {
285 		((MockSystemReader) SystemReader.getInstance()).setWindows();
286 		testMaliciousPathGoodFirstCheckout("lpt"); // good name
287 	}
288 
289 	@Test
290 	public void testForbiddenNamesOnWindowsEgCon1() throws Exception {
291 		((MockSystemReader) SystemReader.getInstance()).setWindows();
292 		testMaliciousPathGoodFirstCheckout("con1"); // good name
293 	}
294 
295 	@Test
296 	public void testForbiddenWindowsNamesOnUnixEgCon() throws Exception {
297 		if (File.separatorChar == '\\')
298 			return; // cannot emulate Unix on Windows for this test
299 		testMaliciousPathGoodFirstCheckout("con");
300 	}
301 
302 	@Test
303 	public void testForbiddenWindowsNamesOnUnixEgLpt1() throws Exception {
304 		if (File.separatorChar == '\\')
305 			return; // cannot emulate Unix on Windows for this test
306 		testMaliciousPathGoodFirstCheckout("lpt1");
307 	}
308 
309 	private void testMaliciousPathBadFirstCheckout(String... paths)
310 			throws Exception {
311 		testMaliciousPath(false, false, paths);
312 	}
313 
314 	private void testMaliciousPathBadSecondCheckout(String... paths) throws Exception {
315 		testMaliciousPath(false, true, paths);
316 	}
317 
318 	private void testMaliciousPathGoodFirstCheckout(String... paths)
319 			throws Exception {
320 		testMaliciousPath(true, false, paths);
321 	}
322 
323 	private void testMaliciousPathGoodSecondCheckout(String... paths) throws Exception {
324 		testMaliciousPath(true, true, paths);
325 	}
326 
327 	/**
328 	 * Create a bad tree and tries to check it out
329 	 *
330 	 * @param good
331 	 *            true if we expect this to pass
332 	 * @param secondCheckout
333 	 *            perform the actual test on the second checkout
334 	 * @param path
335 	 *            to the blob, one or more levels
336 	 * @throws GitAPIException
337 	 * @throws IOException
338 	 */
339 	private void testMaliciousPath(boolean good, boolean secondCheckout,
340 			String... path) throws GitAPIException, IOException {
341 		try (Git git = new Git(db);
342 				RevWalk revWalk = new RevWalk(git.getRepository())) {
343 			ObjectInserter newObjectInserter;
344 			newObjectInserter = git.getRepository().newObjectInserter();
345 			ObjectId blobId = newObjectInserter.insert(Constants.OBJ_BLOB,
346 					"data".getBytes());
347 			newObjectInserter = git.getRepository().newObjectInserter();
348 			FileMode mode = FileMode.REGULAR_FILE;
349 			ObjectId insertId = blobId;
350 			for (int i = path.length - 1; i >= 0; --i) {
351 				TreeFormatter treeFormatter = new TreeFormatter();
352 				treeFormatter.append("goodpath", mode, insertId);
353 				insertId = newObjectInserter.insert(treeFormatter);
354 				mode = FileMode.TREE;
355 			}
356 			newObjectInserter = git.getRepository().newObjectInserter();
357 			CommitBuilder commitBuilder = new CommitBuilder();
358 			commitBuilder.setAuthor(author);
359 			commitBuilder.setCommitter(committer);
360 			commitBuilder.setMessage("foo#1");
361 			commitBuilder.setTreeId(insertId);
362 			ObjectId firstCommitId = newObjectInserter.insert(commitBuilder);
363 
364 			newObjectInserter = git.getRepository().newObjectInserter();
365 			mode = FileMode.REGULAR_FILE;
366 			insertId = blobId;
367 			for (int i = path.length - 1; i >= 0; --i) {
368 				TreeFormatter treeFormatter = new TreeFormatter();
369 				treeFormatter.append(path[i].getBytes(), 0,
370 							path[i].getBytes().length,
371 							mode, insertId, true);
372 				insertId = newObjectInserter.insert(treeFormatter);
373 				mode = FileMode.TREE;
374 			}
375 
376 			// Create another commit
377 			commitBuilder = new CommitBuilder();
378 			commitBuilder.setAuthor(author);
379 			commitBuilder.setCommitter(committer);
380 			commitBuilder.setMessage("foo#2");
381 			commitBuilder.setTreeId(insertId);
382 			commitBuilder.setParentId(firstCommitId);
383 			ObjectId commitId = newObjectInserter.insert(commitBuilder);
384 
385 			if (!secondCheckout)
386 				git.checkout().setStartPoint(revWalk.parseCommit(firstCommitId))
387 						.setName("refs/heads/master").setCreateBranch(true).call();
388 			try {
389 				if (secondCheckout) {
390 					git.checkout().setStartPoint(revWalk.parseCommit(commitId))
391 							.setName("refs/heads/master").setCreateBranch(true)
392 							.call();
393 				} else {
394 					git.branchCreate().setName("refs/heads/next")
395 							.setStartPoint(commitId.name()).call();
396 					git.checkout().setName("refs/heads/next")
397 							.call();
398 				}
399 				if (!good)
400 					fail("Checkout of Tree " + Arrays.asList(path) + " should fail");
401 			} catch (InvalidPathException e) {
402 				if (good)
403 					throw e;
404 				assertTrue(e.getMessage().startsWith("Invalid path"));
405 			}
406 		}
407 	}
408 
409 }