Skip to content

Commit 5082f8e

Browse files
author
Vladimir Kotal
authored
sanitize path in {History/Annotation}Controller (#3794)
* sanitize path * extract the check to toFile() * use InvalidPathException * remove unused exception * remove unused import * add warning suppression * fix tests * add more coverage * update javadoc * try to escape * use relative path * refactor
1 parent 0dd2007 commit 5082f8e

File tree

5 files changed

+81
-16
lines changed

5 files changed

+81
-16
lines changed

opengrok-web/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,11 @@ Portions Copyright (c) 2018, 2020, Chris Fraire <[email protected]>.
199199
<groupId>org.jetbrains</groupId>
200200
<artifactId>annotations</artifactId>
201201
</dependency>
202+
<dependency>
203+
<groupId>org.junit.jupiter</groupId>
204+
<artifactId>junit-jupiter-params</artifactId>
205+
<scope>test</scope>
206+
</dependency>
202207
</dependencies>
203208

204209
<build>

opengrok-web/src/main/java/org/opengrok/web/api/v1/controller/AnnotationController.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444

4545
import static org.opengrok.web.util.FileUtil.toFile;
4646

47+
// No need to have PATH configurable.
48+
@SuppressWarnings("java:S1075")
4749
@Path(AnnotationController.PATH)
4850
public class AnnotationController {
4951

opengrok-web/src/main/java/org/opengrok/web/api/v1/controller/HistoryController.java

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,28 +33,31 @@
3333
import jakarta.ws.rs.core.Context;
3434
import jakarta.ws.rs.core.MediaType;
3535
import org.jetbrains.annotations.TestOnly;
36-
import org.opengrok.indexer.configuration.RuntimeEnvironment;
3736
import org.opengrok.indexer.history.History;
3837
import org.opengrok.indexer.history.HistoryEntry;
3938
import org.opengrok.indexer.history.HistoryException;
4039
import org.opengrok.indexer.history.HistoryGuru;
4140
import org.opengrok.indexer.web.messages.JSONable;
4241
import org.opengrok.web.api.v1.filter.CorsEnable;
4342
import org.opengrok.web.api.v1.filter.PathAuthorized;
43+
import org.opengrok.web.util.NoPathParameterException;
4444

4545
import java.io.File;
46+
import java.io.IOException;
4647
import java.util.ArrayList;
4748
import java.util.Date;
4849
import java.util.List;
4950
import java.util.Map;
5051
import java.util.Objects;
5152
import java.util.SortedSet;
5253

54+
import static org.opengrok.web.util.FileUtil.toFile;
55+
56+
// No need to have PATH configurable.
57+
@SuppressWarnings("java:S1075")
5358
@Path(HistoryController.PATH)
5459
public final class HistoryController {
5560

56-
private static final RuntimeEnvironment env = RuntimeEnvironment.getInstance();
57-
5861
private static final int MAX_RESULTS = 1000;
5962

6063
public static final String PATH = "/history";
@@ -204,10 +207,11 @@ public HistoryDTO get(@Context HttpServletRequest request,
204207
@QueryParam("withFiles") final boolean withFiles,
205208
@QueryParam("max") @DefaultValue(MAX_RESULTS + "") final int maxEntries,
206209
@QueryParam("start") @DefaultValue(0 + "") final int startIndex)
207-
throws HistoryException {
210+
throws HistoryException, IOException, NoPathParameterException {
211+
212+
File file = toFile(path);
208213

209-
History history = HistoryGuru.getInstance().getHistory(new File(env.getSourceRootFile(), path),
210-
withFiles, true);
214+
History history = HistoryGuru.getInstance().getHistory(file, withFiles, true);
211215
if (history == null) {
212216
return null;
213217
}

opengrok-web/src/main/java/org/opengrok/web/util/FileUtil.java

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,16 @@
1818
*/
1919

2020
/*
21-
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
21+
* Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved.
2222
*/
2323
package org.opengrok.web.util;
2424

2525
import org.opengrok.indexer.configuration.RuntimeEnvironment;
2626

2727
import java.io.File;
2828
import java.io.FileNotFoundException;
29+
import java.io.IOException;
30+
import java.nio.file.InvalidPathException;
2931

3032
public class FileUtil {
3133

@@ -38,17 +40,25 @@ private FileUtil() {
3840
/**
3941
* @param path path relative to source root
4042
* @return file object corresponding to the file under source root
41-
* @throws FileNotFoundException if the file constructed from the {@code path} parameter and source root does not exist
43+
* @throws FileNotFoundException if the file constructed from the {@code path} parameter and source root
44+
* does not exist
45+
* @throws InvalidPathException if the file constructed from the {@code path} parameter and source root
46+
* leads outside source root
4247
* @throws NoPathParameterException if the {@code path} parameter is null
4348
*/
44-
public static File toFile(String path) throws NoPathParameterException, FileNotFoundException {
49+
public static File toFile(String path) throws NoPathParameterException, IOException {
4550
if (path == null) {
4651
throw new NoPathParameterException("Missing path parameter");
4752
}
4853

4954
File file = new File(env.getSourceRootFile(), path);
50-
if (!file.isFile()) {
51-
throw new FileNotFoundException("File " + file.toString() + "not found");
55+
56+
if (!file.getCanonicalPath().startsWith(env.getSourceRootPath() + File.separator)) {
57+
throw new InvalidPathException(path, "File points to outside of source root");
58+
}
59+
60+
if (!file.exists()) {
61+
throw new FileNotFoundException("File " + file + " not found");
5262
}
5363

5464
return file;

opengrok-web/src/test/java/org/opengrok/web/util/FileUtilTest.java

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,31 +18,75 @@
1818
*/
1919

2020
/*
21+
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
2122
* Copyright (c) 2020, Chris Fraire <[email protected]>.
2223
*/
2324
package org.opengrok.web.util;
2425

2526
import org.junit.jupiter.api.Test;
27+
import org.junit.jupiter.params.ParameterizedTest;
28+
import org.junit.jupiter.params.provider.ValueSource;
29+
import org.opengrok.indexer.configuration.RuntimeEnvironment;
2630

31+
import static org.junit.jupiter.api.Assertions.assertNotNull;
2732
import static org.junit.jupiter.api.Assertions.assertThrows;
33+
import static org.junit.jupiter.api.Assertions.assertTrue;
2834

35+
import java.io.File;
2936
import java.io.FileNotFoundException;
37+
import java.io.IOException;
38+
import java.nio.file.Files;
39+
import java.nio.file.InvalidPathException;
40+
import java.nio.file.Path;
41+
import java.nio.file.Paths;
3042
import java.util.UUID;
3143

3244
/**
3345
* Represents a container for tests of {@link FileUtil}.
3446
*/
35-
public class FileUtilTest {
47+
class FileUtilTest {
48+
49+
RuntimeEnvironment env = RuntimeEnvironment.getInstance();
3650

3751
@Test
38-
public void shouldThrowOnNullArgument() {
52+
void shouldThrowOnNullArgument() {
3953
assertThrows(NoPathParameterException.class, () -> FileUtil.toFile(null),
4054
"toFile(null)");
4155
}
4256

57+
private String setSourceRoot(String id) throws IOException {
58+
String origRoot = env.getSourceRootPath();
59+
Path dir = Files.createTempDirectory(id);
60+
dir.toFile().deleteOnExit();
61+
env.setSourceRoot(dir.toString());
62+
assertTrue(env.getSourceRootFile().isDirectory());
63+
return origRoot;
64+
}
65+
4366
@Test
44-
public void shouldThrowOnMissingFile() {
45-
assertThrows(FileNotFoundException.class, () -> FileUtil.toFile(
46-
UUID.randomUUID().toString()), "toFile(randomUUID)");
67+
void shouldThrowOnInvalidFile() throws IOException {
68+
String origRoot = setSourceRoot("shouldThrowOnInvalidFile");
69+
String rndPath = ".." + File.separator + UUID.randomUUID();
70+
assertThrows(InvalidPathException.class, () -> FileUtil.toFile(rndPath),
71+
"toFile(randomUUID)");
72+
env.setSourceRoot(origRoot);
73+
}
74+
75+
@ParameterizedTest
76+
@ValueSource(booleans = {true, false})
77+
void shouldThrowOnMissingFile(boolean isPresent) throws IOException, NoPathParameterException {
78+
String origRoot = setSourceRoot("shouldThrowOnMissingFile");
79+
if (isPresent) {
80+
String fileName = "existent";
81+
Path filePath = Paths.get(env.getSourceRootPath(), fileName);
82+
Files.createFile(filePath);
83+
filePath.toFile().deleteOnExit();
84+
assertTrue(filePath.toFile().exists());
85+
assertNotNull(FileUtil.toFile(fileName));
86+
} else {
87+
assertThrows(FileNotFoundException.class, () -> FileUtil.toFile("nonexistent"),
88+
"toFile(nonexistent)");
89+
}
90+
env.setSourceRoot(origRoot);
4791
}
4892
}

0 commit comments

Comments
 (0)