Skip to content

Commit

Permalink
Merge pull request #23862 from yrodiere/pathtree-forbid-dotdot
Browse files Browse the repository at this point in the history
Forbid using '..' in paths passed to PathTree
  • Loading branch information
aloubyansky authored Feb 22, 2022
2 parents 95cc838 + f654089 commit 2239a38
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,13 @@ public void walk(PathVisitor visitor) {
}
}

private void ensureResourcePath(String path) {
DirectoryPathTree.ensureResourcePath(archive.getFileSystem(), path);
}

@Override
protected <T> T apply(String relativePath, Function<PathVisit, T> func, boolean manifestEnabled) {
DirectoryPathTree.ensureRelativePath(relativePath);
ensureResourcePath(relativePath);
if (!PathFilter.isVisible(pathFilter, relativePath)) {
return func.apply(null);
}
Expand All @@ -74,7 +78,7 @@ protected <T> T apply(String relativePath, Function<PathVisit, T> func, boolean

@Override
public void accept(String relativePath, Consumer<PathVisit> consumer) {
DirectoryPathTree.ensureRelativePath(relativePath);
ensureResourcePath(relativePath);
if (!PathFilter.isVisible(pathFilter, relativePath)) {
consumer.accept(null);
return;
Expand All @@ -99,7 +103,7 @@ public void accept(String relativePath, Consumer<PathVisit> consumer) {

@Override
public boolean contains(String relativePath) {
DirectoryPathTree.ensureRelativePath(relativePath);
ensureResourcePath(relativePath);
if (!PathFilter.isVisible(pathFilter, relativePath)) {
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.io.IOException;
import java.io.Serializable;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
Expand Down Expand Up @@ -34,10 +35,18 @@ static boolean isAbsolutePath(String path) {
|| path.startsWith(FileSystems.getDefault().getSeparator()));
}

static void ensureRelativePath(String path) {
static void ensureResourcePath(FileSystem fs, String path) {
if (isAbsolutePath(path)) {
throw new IllegalArgumentException("Expected a path relative to the root of the path tree but got " + path);
}
// this is to disallow reading outside the path tree root
if (path != null && path.contains("..")) {
for (Path pathElement : fs.getPath(path)) {
if (pathElement.toString().equals("..")) {
throw new IllegalArgumentException("'..' cannot be used in resource paths, but got " + path);
}
}
}
}

private Path dir;
Expand Down Expand Up @@ -80,22 +89,13 @@ public void walk(PathVisitor visitor) {
PathTreeVisit.walk(dir, dir, pathFilter, getMultiReleaseMapping(), visitor);
}

private String normalize(String path) {
ensureRelativePath(path);
// this is to disallow reading outside the path tree root
if (path != null && path.contains("..")) {
final Path absolutePath = dir.resolve(path).normalize().toAbsolutePath();
if (absolutePath.startsWith(dir)) {
return dir.relativize(absolutePath).toString();
}
return null;
}
return path;
private void ensureResourcePath(String path) {
ensureResourcePath(dir.getFileSystem(), path);
}

@Override
protected <T> T apply(String relativePath, Function<PathVisit, T> func, boolean manifestEnabled) {
relativePath = normalize(relativePath);
ensureResourcePath(relativePath);
if (!PathFilter.isVisible(pathFilter, relativePath)) {
return func.apply(null);
}
Expand All @@ -108,7 +108,7 @@ protected <T> T apply(String relativePath, Function<PathVisit, T> func, boolean

@Override
public void accept(String relativePath, Consumer<PathVisit> consumer) {
relativePath = normalize(relativePath);
ensureResourcePath(relativePath);
if (!PathFilter.isVisible(pathFilter, relativePath)) {
consumer.accept(null);
return;
Expand All @@ -123,7 +123,7 @@ public void accept(String relativePath, Consumer<PathVisit> consumer) {

@Override
public boolean contains(String relativePath) {
relativePath = normalize(relativePath);
ensureResourcePath(relativePath);
if (!PathFilter.isVisible(pathFilter, relativePath)) {
return false;
}
Expand All @@ -133,7 +133,7 @@ public boolean contains(String relativePath) {

@Override
public Path getPath(String relativePath) {
relativePath = normalize(relativePath);
ensureResourcePath(relativePath);
if (!PathFilter.isVisible(pathFilter, relativePath)) {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.quarkus.paths;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.fail;

import java.io.IOException;
Expand Down Expand Up @@ -89,26 +90,39 @@ public void acceptIllegalAbsolutePathOutsideTree() throws Exception {
public void acceptExistingRelativeNonNormalizedPath() throws Exception {
final Path root = resolveTreeRoot("root");
final PathTree tree = PathTree.ofDirectoryOrArchive(root);
tree.accept("../root/./other/../README.md", visit -> {
assertThat(visit).isNotNull();
assertThat(visit.getRelativePath("/")).isEqualTo("README.md");
assertThat(visit.getPath()).exists();
assertThat(visit.getRoot()).isEqualTo(root);
try {
assertThat(Files.readString(visit.getPath())).isEqualTo("test readme");
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
assertThatThrownBy(() -> {
tree.accept("other/../README.md", visit -> {
fail("'..' should lead to an exception that prevents the visitor from being called");
});
})
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("'..' cannot be used in resource paths");
}

@Test
public void acceptNonExistentRelativeNonNormalizedPath() throws Exception {
public void acceptExistingRelativeNonNormalizedPathOutsideTree() throws Exception {
final Path root = resolveTreeRoot("root");
final PathTree tree = PathTree.ofDirectoryOrArchive(root);
tree.accept("../root/./README.md/../non-existent.txt", visit -> {
assertThat(visit).isNull();
});
assertThatThrownBy(() -> {
tree.accept("../root/./other/../README.md", visit -> {
fail("'..' should lead to an exception that prevents the visitor from being called");
});
})
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("'..' cannot be used in resource paths");
}

@Test
public void acceptNonExistentRelativeNonNormalizedPathOutsideTree() throws Exception {
final Path root = resolveTreeRoot("root");
final PathTree tree = PathTree.ofDirectoryOrArchive(root);
assertThatThrownBy(() -> {
tree.accept("../root/./README.md/../non-existent.txt", visit -> {
fail("'..' should lead to an exception that prevents the visitor from being called");
});
})
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("'..' cannot be used in resource paths");
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.quarkus.paths;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.fail;

import io.quarkus.fs.util.ZipUtils;
Expand Down Expand Up @@ -85,6 +86,42 @@ public void acceptAbsolutePathOutsideTree() throws Exception {
}
}

@Test
public void acceptExistingRelativeNonNormalizedPath() throws Exception {
final PathTree tree = PathTree.ofDirectoryOrArchive(root);
assertThatThrownBy(() -> {
tree.accept("other/../README.md", visit -> {
fail("'..' should lead to an exception that prevents the visitor from being called");
});
})
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("'..' cannot be used in resource paths");
}

@Test
public void acceptExistingRelativeNonNormalizedPathOutsideTree() throws Exception {
final PathTree tree = PathTree.ofDirectoryOrArchive(root);
assertThatThrownBy(() -> {
tree.accept("../root/./other/../README.md", visit -> {
fail("'..' should lead to an exception that prevents the visitor from being called");
});
})
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("'..' cannot be used in resource paths");
}

@Test
public void acceptNonExistentRelativeNonNormalizedPathOutsideTree() throws Exception {
final PathTree tree = PathTree.ofDirectoryOrArchive(root);
assertThatThrownBy(() -> {
tree.accept("../root/./README.md/../non-existent.txt", visit -> {
fail("'..' should lead to an exception that prevents the visitor from being called");
});
})
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("'..' cannot be used in resource paths");
}

@Test
public void walk() throws Exception {
final PathTree tree = PathTree.ofDirectoryOrArchive(root);
Expand Down

0 comments on commit 2239a38

Please sign in to comment.