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 java.nio.charset.StandardCharsets.UTF_8;
41  import static org.junit.Assert.assertTrue;
42  import static org.junit.Assert.fail;
43  
44  import java.io.File;
45  import java.io.IOException;
46  import java.util.Arrays;
47  
48  import org.eclipse.jgit.api.Git;
49  import org.eclipse.jgit.api.errors.GitAPIException;
50  import org.eclipse.jgit.dircache.InvalidPathException;
51  import org.eclipse.jgit.junit.MockSystemReader;
52  import org.eclipse.jgit.junit.RepositoryTestCase;
53  import org.eclipse.jgit.revwalk.RevWalk;
54  import org.eclipse.jgit.util.SystemReader;
55  import org.junit.Test;
56  
57  public class DirCacheCheckoutMaliciousPathTest extends RepositoryTestCase {
58  
59  	protected ObjectId theHead;
60  	protected ObjectId theMerge;
61  
62  	@Test
63  	public void testMaliciousAbsolutePathIsOk() throws Exception {
64  		testMaliciousPathGoodFirstCheckout("ok");
65  	}
66  
67  	@Test
68  	public void testMaliciousAbsolutePathIsOkSecondCheckout() throws Exception {
69  		testMaliciousPathGoodSecondCheckout("ok");
70  	}
71  
72  	@Test
73  	public void testMaliciousAbsolutePathIsOkTwoLevels() throws Exception {
74  		testMaliciousPathGoodSecondCheckout("a", "ok");
75  	}
76  
77  	@Test
78  	public void testMaliciousAbsolutePath() throws Exception {
79  		testMaliciousPathBadFirstCheckout("/tmp/x");
80  	}
81  
82  	@Test
83  	public void testMaliciousAbsolutePathSecondCheckout() throws Exception {
84  		testMaliciousPathBadSecondCheckout("/tmp/x");
85  	}
86  
87  	@Test
88  	public void testMaliciousAbsolutePathTwoLevelsFirstBad() throws Exception {
89  		testMaliciousPathBadFirstCheckout("/tmp/x", "y");
90  	}
91  
92  	@Test
93  	public void testMaliciousAbsolutePathTwoLevelsSecondBad() throws Exception {
94  		testMaliciousPathBadFirstCheckout("y", "/tmp/x");
95  	}
96  
97  	@Test
98  	public void testMaliciousAbsoluteCurDrivePathWindows() throws Exception {
99  		((MockSystemReader) SystemReader.getInstance()).setWindows();
100 		testMaliciousPathBadFirstCheckout("\\somepath");
101 	}
102 
103 	@Test
104 	public void testMaliciousAbsoluteCurDrivePathWindowsOnUnix()
105 			throws Exception {
106 		((MockSystemReader) SystemReader.getInstance()).setUnix();
107 		testMaliciousPathGoodFirstCheckout("\\somepath");
108 	}
109 
110 	@Test
111 	public void testMaliciousAbsoluteUNCPathWindows1() throws Exception {
112 		((MockSystemReader) SystemReader.getInstance()).setWindows();
113 		testMaliciousPathBadFirstCheckout("\\\\somepath");
114 	}
115 
116 	@Test
117 	public void testMaliciousAbsoluteUNCPathWindows1OnUnix() throws Exception {
118 		((MockSystemReader) SystemReader.getInstance()).setUnix();
119 		testMaliciousPathGoodFirstCheckout("\\\\somepath");
120 	}
121 
122 	@Test
123 	public void testMaliciousAbsoluteUNCPathWindows2() throws Exception {
124 		((MockSystemReader) SystemReader.getInstance()).setWindows();
125 		testMaliciousPathBadFirstCheckout("\\/somepath");
126 	}
127 
128 	@Test
129 	public void testMaliciousAbsoluteUNCPathWindows2OnUnix() throws Exception {
130 		((MockSystemReader) SystemReader.getInstance()).setUnix();
131 		testMaliciousPathBadFirstCheckout("\\/somepath");
132 	}
133 
134 	@Test
135 	public void testMaliciousAbsoluteWindowsPath1() throws Exception {
136 		((MockSystemReader) SystemReader.getInstance()).setWindows();
137 		testMaliciousPathBadFirstCheckout("c:\\temp\\x");
138 	}
139 
140 	@Test
141 	public void testMaliciousAbsoluteWindowsPath1OnUnix() throws Exception {
142 		if (File.separatorChar == '\\')
143 			return; // cannot emulate Unix on Windows for this test
144 		((MockSystemReader) SystemReader.getInstance()).setUnix();
145 		testMaliciousPathGoodFirstCheckout("c:\\temp\\x");
146 	}
147 
148 	@Test
149 	public void testMaliciousAbsoluteWindowsPath2() throws Exception {
150 		((MockSystemReader) SystemReader.getInstance()).setCurrentPlatform();
151 		testMaliciousPathBadFirstCheckout("c:/temp/x");
152 	}
153 
154 	@Test
155 	public void testMaliciousGitPath1() throws Exception {
156 		testMaliciousPathBadFirstCheckout(".git/konfig");
157 	}
158 
159 	@Test
160 	public void testMaliciousGitPath2() throws Exception {
161 		testMaliciousPathBadFirstCheckout(".git", "konfig");
162 	}
163 
164 	@Test
165 	public void testMaliciousGitPath1Case() throws Exception {
166 		((MockSystemReader) SystemReader.getInstance()).setWindows(); // or OS X
167 		testMaliciousPathBadFirstCheckout(".Git/konfig");
168 	}
169 
170 	@Test
171 	public void testMaliciousGitPath2Case() throws Exception {
172 		((MockSystemReader) SystemReader.getInstance()).setWindows(); // or OS X
173 		testMaliciousPathBadFirstCheckout(".gIt", "konfig");
174 	}
175 
176 	@Test
177 	public void testMaliciousGitPath3Case() throws Exception {
178 		((MockSystemReader) SystemReader.getInstance()).setWindows(); // or OS X
179 		testMaliciousPathBadFirstCheckout(".giT", "konfig");
180 	}
181 
182 	@Test
183 	public void testMaliciousGitPathEndSpaceWindows() throws Exception {
184 		((MockSystemReader) SystemReader.getInstance()).setWindows();
185 		testMaliciousPathBadFirstCheckout(".git ", "konfig");
186 	}
187 
188 	@Test
189 	public void testMaliciousGitPathEndSpaceUnixOk() throws Exception {
190 		testMaliciousPathBadFirstCheckout(".git ", "konfig");
191 	}
192 
193 	@Test
194 	public void testMaliciousGitPathEndDotWindows1() throws Exception {
195 		((MockSystemReader) SystemReader.getInstance()).setWindows();
196 		testMaliciousPathBadFirstCheckout(".git.", "konfig");
197 	}
198 
199 	@Test
200 	public void testMaliciousGitPathEndDotWindows2() throws Exception {
201 		((MockSystemReader) SystemReader.getInstance()).setWindows();
202 		testMaliciousPathBadFirstCheckout(".f.");
203 	}
204 
205 	@Test
206 	public void testMaliciousGitPathEndDotWindows3() throws Exception {
207 		((MockSystemReader) SystemReader.getInstance()).setWindows();
208 		testMaliciousPathGoodFirstCheckout(".f");
209 	}
210 
211 	@Test
212 	public void testMaliciousGitPathEndDotUnixOk() throws Exception {
213 		testMaliciousPathBadFirstCheckout(".git.", "konfig");
214 	}
215 
216 	@Test
217 	public void testMaliciousPathDotDot() throws Exception {
218 		((MockSystemReader) SystemReader.getInstance()).setCurrentPlatform();
219 		testMaliciousPathBadFirstCheckout("..", "no");
220 	}
221 
222 	@Test
223 	public void testMaliciousPathDot() throws Exception {
224 		((MockSystemReader) SystemReader.getInstance()).setCurrentPlatform();
225 		testMaliciousPathBadFirstCheckout(".", "no");
226 	}
227 
228 	@Test
229 	public void testMaliciousPathEmptyUnix() throws Exception {
230 		((MockSystemReader) SystemReader.getInstance()).setUnix();
231 		testMaliciousPathBadFirstCheckout("", "no");
232 	}
233 
234 	@Test
235 	public void testMaliciousPathEmptyWindows() throws Exception {
236 		((MockSystemReader) SystemReader.getInstance()).setWindows();
237 		testMaliciousPathBadFirstCheckout("", "no");
238 	}
239 
240 	@Test
241 	public void testMaliciousWindowsADS() throws Exception {
242 		((MockSystemReader) SystemReader.getInstance()).setWindows();
243 		testMaliciousPathBadFirstCheckout("some:path");
244 	}
245 
246 	@Test
247 	public void testMaliciousWindowsADSOnUnix() throws Exception {
248 		if (File.separatorChar == '\\')
249 			return; // cannot emulate Unix on Windows for this test
250 		((MockSystemReader) SystemReader.getInstance()).setUnix();
251 		testMaliciousPathGoodFirstCheckout("some:path");
252 	}
253 
254 	@Test
255 	public void testForbiddenNamesOnWindowsEgCon() throws Exception {
256 		((MockSystemReader) SystemReader.getInstance()).setWindows();
257 		testMaliciousPathBadFirstCheckout("con");
258 	}
259 
260 	@Test
261 	public void testForbiddenNamesOnWindowsEgConDotSuffix() throws Exception {
262 		((MockSystemReader) SystemReader.getInstance()).setWindows();
263 		testMaliciousPathBadFirstCheckout("con.txt");
264 	}
265 
266 	@Test
267 	public void testForbiddenNamesOnWindowsEgLpt1() throws Exception {
268 		((MockSystemReader) SystemReader.getInstance()).setWindows();
269 		testMaliciousPathBadFirstCheckout("lpt1");
270 	}
271 
272 	@Test
273 	public void testForbiddenNamesOnWindowsEgLpt1DotSuffix() throws Exception {
274 		((MockSystemReader) SystemReader.getInstance()).setWindows();
275 		testMaliciousPathBadFirstCheckout("lpt1.txt");
276 	}
277 
278 	@Test
279 	public void testForbiddenNamesOnWindowsEgDotCon() throws Exception {
280 		((MockSystemReader) SystemReader.getInstance()).setWindows();
281 		testMaliciousPathGoodFirstCheckout(".con");
282 	}
283 
284 	@Test
285 	public void testForbiddenNamesOnWindowsEgLpr() throws Exception {
286 		((MockSystemReader) SystemReader.getInstance()).setWindows();
287 		testMaliciousPathGoodFirstCheckout("lpt"); // good name
288 	}
289 
290 	@Test
291 	public void testForbiddenNamesOnWindowsEgCon1() throws Exception {
292 		((MockSystemReader) SystemReader.getInstance()).setWindows();
293 		testMaliciousPathGoodFirstCheckout("con1"); // good name
294 	}
295 
296 	@Test
297 	public void testForbiddenWindowsNamesOnUnixEgCon() throws Exception {
298 		if (File.separatorChar == '\\')
299 			return; // cannot emulate Unix on Windows for this test
300 		testMaliciousPathGoodFirstCheckout("con");
301 	}
302 
303 	@Test
304 	public void testForbiddenWindowsNamesOnUnixEgLpt1() throws Exception {
305 		if (File.separatorChar == '\\')
306 			return; // cannot emulate Unix on Windows for this test
307 		testMaliciousPathGoodFirstCheckout("lpt1");
308 	}
309 
310 	private void testMaliciousPathBadFirstCheckout(String... paths)
311 			throws Exception {
312 		testMaliciousPath(false, false, paths);
313 	}
314 
315 	private void testMaliciousPathBadSecondCheckout(String... paths) throws Exception {
316 		testMaliciousPath(false, true, paths);
317 	}
318 
319 	private void testMaliciousPathGoodFirstCheckout(String... paths)
320 			throws Exception {
321 		testMaliciousPath(true, false, paths);
322 	}
323 
324 	private void testMaliciousPathGoodSecondCheckout(String... paths) throws Exception {
325 		testMaliciousPath(true, true, paths);
326 	}
327 
328 	/**
329 	 * Create a bad tree and tries to check it out
330 	 *
331 	 * @param good
332 	 *            true if we expect this to pass
333 	 * @param secondCheckout
334 	 *            perform the actual test on the second checkout
335 	 * @param path
336 	 *            to the blob, one or more levels
337 	 * @throws GitAPIException
338 	 * @throws IOException
339 	 */
340 	private void testMaliciousPath(boolean good, boolean secondCheckout,
341 			String... path) throws GitAPIException, IOException {
342 		try (Git git = new Git(db);
343 				RevWalk revWalk = new RevWalk(git.getRepository())) {
344 			ObjectId blobId;
345 			try (ObjectInserter newObjectInserter = git.getRepository()
346 					.newObjectInserter()) {
347 				blobId = newObjectInserter.insert(Constants.OBJ_BLOB,
348 					"data".getBytes(UTF_8));
349 			}
350 			FileMode mode = FileMode.REGULAR_FILE;
351 			ObjectId insertId = blobId;
352 			try (ObjectInserter newObjectInserter = git.getRepository()
353 					.newObjectInserter()) {
354 				for (int i = path.length - 1; i >= 0; --i) {
355 					TreeFormatter treeFormatter = new TreeFormatter();
356 					treeFormatter.append("goodpath", mode, insertId);
357 					insertId = newObjectInserter.insert(treeFormatter);
358 					mode = FileMode.TREE;
359 				}
360 			}
361 			ObjectId firstCommitId;
362 			try (ObjectInserter newObjectInserter = git.getRepository()
363 					.newObjectInserter()) {
364 				CommitBuilder commitBuilder = new CommitBuilder();
365 				commitBuilder.setAuthor(author);
366 				commitBuilder.setCommitter(committer);
367 				commitBuilder.setMessage("foo#1");
368 				commitBuilder.setTreeId(insertId);
369 				firstCommitId = newObjectInserter.insert(commitBuilder);
370 			}
371 			ObjectId commitId;
372 			try (ObjectInserter newObjectInserter = git.getRepository()
373 					.newObjectInserter()) {
374 				mode = FileMode.REGULAR_FILE;
375 				insertId = blobId;
376 				for (int i = path.length - 1; i >= 0; --i) {
377 					TreeFormatter treeFormatter = new TreeFormatter();
378 					treeFormatter.append(path[i].getBytes(UTF_8), 0,
379 							path[i].getBytes(UTF_8).length, mode, insertId,
380 							true);
381 					insertId = newObjectInserter.insert(treeFormatter);
382 					mode = FileMode.TREE;
383 				}
384 
385 				// Create another commit
386 				CommitBuilder commitBuilder = new CommitBuilder();
387 				commitBuilder.setAuthor(author);
388 				commitBuilder.setCommitter(committer);
389 				commitBuilder.setMessage("foo#2");
390 				commitBuilder.setTreeId(insertId);
391 				commitBuilder.setParentId(firstCommitId);
392 				commitId = newObjectInserter.insert(commitBuilder);
393 			}
394 			if (!secondCheckout)
395 				git.checkout().setStartPoint(revWalk.parseCommit(firstCommitId))
396 						.setName("refs/heads/master").setCreateBranch(true).call();
397 			try {
398 				if (secondCheckout) {
399 					git.checkout().setStartPoint(revWalk.parseCommit(commitId))
400 							.setName("refs/heads/master").setCreateBranch(true)
401 							.call();
402 				} else {
403 					git.branchCreate().setName("refs/heads/next")
404 							.setStartPoint(commitId.name()).call();
405 					git.checkout().setName("refs/heads/next")
406 							.call();
407 				}
408 				if (!good)
409 					fail("Checkout of Tree " + Arrays.asList(path) + " should fail");
410 			} catch (InvalidPathException e) {
411 				if (good)
412 					throw e;
413 				assertTrue(e.getMessage().startsWith("Invalid path"));
414 			}
415 		}
416 	}
417 
418 }