Skip to content

Commit 763e7cd

Browse files
authored
FileAccessTree fixes for ordering and pruning (#123291)
* Custom comparator for paths in FileAccessTree * Strip trailing separators in normalizePath
1 parent 5664f4f commit 763e7cd

File tree

2 files changed

+63
-7
lines changed

2 files changed

+63
-7
lines changed

libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/FileAccessTree.java

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import java.nio.file.Path;
1515
import java.util.ArrayList;
1616
import java.util.Arrays;
17+
import java.util.Comparator;
1718
import java.util.List;
1819
import java.util.Objects;
1920

@@ -46,8 +47,8 @@ private FileAccessTree(FilesEntitlement filesEntitlement, PathLookup pathLookup)
4647
readPaths.add(tempDir);
4748
writePaths.add(tempDir);
4849

49-
readPaths.sort(String::compareTo);
50-
writePaths.sort(String::compareTo);
50+
readPaths.sort(PATH_ORDER);
51+
writePaths.sort(PATH_ORDER);
5152

5253
this.readPaths = pruneSortedPaths(readPaths).toArray(new String[0]);
5354
this.writePaths = pruneSortedPaths(writePaths).toArray(new String[0]);
@@ -60,7 +61,7 @@ private static List<String> pruneSortedPaths(List<String> paths) {
6061
prunedReadPaths.add(currentPath);
6162
for (int i = 1; i < paths.size(); ++i) {
6263
String nextPath = paths.get(i);
63-
if (nextPath.startsWith(currentPath) == false) {
64+
if (isParent(currentPath, nextPath) == false) {
6465
prunedReadPaths.add(nextPath);
6566
currentPath = nextPath;
6667
}
@@ -88,21 +89,28 @@ static String normalizePath(Path path) {
8889
// Note that toAbsolutePath produces paths separated by the default file separator,
8990
// so on Windows, if the given path uses forward slashes, this consistently
9091
// converts it to backslashes.
91-
return path.toAbsolutePath().normalize().toString();
92+
String result = path.toAbsolutePath().normalize().toString();
93+
while (result.endsWith(FILE_SEPARATOR)) {
94+
result = result.substring(0, result.length() - FILE_SEPARATOR.length());
95+
}
96+
return result;
9297
}
9398

9499
private static boolean checkPath(String path, String[] paths) {
95100
if (paths.length == 0) {
96101
return false;
97102
}
98-
int ndx = Arrays.binarySearch(paths, path);
103+
int ndx = Arrays.binarySearch(paths, path, PATH_ORDER);
99104
if (ndx < -1) {
100-
String maybeParent = paths[-ndx - 2];
101-
return path.startsWith(maybeParent) && path.startsWith(FILE_SEPARATOR, maybeParent.length());
105+
return isParent(paths[-ndx - 2], path);
102106
}
103107
return ndx >= 0;
104108
}
105109

110+
private static boolean isParent(String maybeParent, String path) {
111+
return path.startsWith(maybeParent) && path.startsWith(FILE_SEPARATOR, maybeParent.length());
112+
}
113+
106114
@Override
107115
public boolean equals(Object o) {
108116
if (o == null || getClass() != o.getClass()) return false;
@@ -114,4 +122,30 @@ public boolean equals(Object o) {
114122
public int hashCode() {
115123
return Objects.hash(Arrays.hashCode(readPaths), Arrays.hashCode(writePaths));
116124
}
125+
126+
/**
127+
* For our lexicographic sort trick to work correctly, we must have path separators sort before
128+
* any other character so that files in a directory appear immediately after that directory.
129+
* For example, we require [/a, /a/b, /a.xml] rather than the natural order [/a, /a.xml, /a/b].
130+
*/
131+
private static final Comparator<String> PATH_ORDER = (s1, s2) -> {
132+
Path p1 = Path.of(s1);
133+
Path p2 = Path.of(s2);
134+
var i1 = p1.iterator();
135+
var i2 = p2.iterator();
136+
while (i1.hasNext() && i2.hasNext()) {
137+
int cmp = i1.next().compareTo(i2.next());
138+
if (cmp != 0) {
139+
return cmp;
140+
}
141+
}
142+
if (i1.hasNext()) {
143+
return 1;
144+
} else if (i2.hasNext()) {
145+
return -1;
146+
} else {
147+
assert p1.equals(p2);
148+
return 0;
149+
}
150+
};
117151
}

libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/FileAccessTreeTests.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,15 @@ public void testPrunedPaths() {
117117
assertThat(tree.canWrite(path("foo/baz")), is(false));
118118
}
119119

120+
public void testPathAndFileWithSamePrefix() {
121+
var tree = accessTree(entitlement("foo/bar/", "read", "foo/bar.xml", "read"));
122+
assertThat(tree.canRead(path("foo")), is(false));
123+
assertThat(tree.canRead(path("foo/bar")), is(true));
124+
assertThat(tree.canRead(path("foo/bar/baz")), is(true));
125+
assertThat(tree.canRead(path("foo/bar.xml")), is(true));
126+
assertThat(tree.canRead(path("foo/bar.txt")), is(false));
127+
}
128+
120129
public void testReadWithRelativePath() {
121130
for (var dir : List.of("config", "home")) {
122131
var tree = accessTree(entitlement(Map.of("relative_path", "foo", "mode", "read", "relative_to", dir)));
@@ -171,10 +180,23 @@ public void testMultipleDataDirs() {
171180
public void testNormalizePath() {
172181
var tree = accessTree(entitlement("foo/../bar", "read"));
173182
assertThat(tree.canRead(path("foo/../bar")), is(true));
183+
assertThat(tree.canRead(path("foo/../bar/")), is(true));
174184
assertThat(tree.canRead(path("foo")), is(false));
175185
assertThat(tree.canRead(path("")), is(false));
176186
}
177187

188+
public void testNormalizeTrailingSlashes() {
189+
var tree = accessTree(entitlement("/trailing/slash/", "read", "/no/trailing/slash", "read"));
190+
assertThat(tree.canRead(path("/trailing/slash")), is(true));
191+
assertThat(tree.canRead(path("/trailing/slash/")), is(true));
192+
assertThat(tree.canRead(path("/trailing/slash.xml")), is(false));
193+
assertThat(tree.canRead(path("/trailing/slash/file.xml")), is(true));
194+
assertThat(tree.canRead(path("/no/trailing/slash")), is(true));
195+
assertThat(tree.canRead(path("/no/trailing/slash/")), is(true));
196+
assertThat(tree.canRead(path("/no/trailing/slash.xml")), is(false));
197+
assertThat(tree.canRead(path("/no/trailing/slash/file.xml")), is(true));
198+
}
199+
178200
public void testForwardSlashes() {
179201
String sep = getDefaultFileSystem().getSeparator();
180202
var tree = accessTree(entitlement("a/b", "read", "m" + sep + "n", "read"));

0 commit comments

Comments
 (0)