diff --git a/docs/changelog/133113.yaml b/docs/changelog/133113.yaml new file mode 100644 index 0000000000000..a5ac7506bf3ce --- /dev/null +++ b/docs/changelog/133113.yaml @@ -0,0 +1,5 @@ +pr: 133113 +summary: Limit the depth of a filter +area: Infra/REST API +type: enhancement +issues: [] diff --git a/libs/x-content/src/main/java/org/elasticsearch/xcontent/support/filtering/FilterPath.java b/libs/x-content/src/main/java/org/elasticsearch/xcontent/support/filtering/FilterPath.java index 0b9aa17ae3e78..a12ab4e6a85c0 100644 --- a/libs/x-content/src/main/java/org/elasticsearch/xcontent/support/filtering/FilterPath.java +++ b/libs/x-content/src/main/java/org/elasticsearch/xcontent/support/filtering/FilterPath.java @@ -22,6 +22,9 @@ public class FilterPath { private static final String WILDCARD = "*"; private static final String DOUBLE_WILDCARD = "**"; + // This is ridiculously large, but we can be 100% certain that if any filter tries to exceed this depth then it is a mistake + static final int MAX_TREE_DEPTH = 500; + private final Map termsChildren; private final FilterPath[] wildcardChildren; private final String pattern; @@ -132,6 +135,7 @@ private boolean matchFieldNamesWithDots(String name, int dotIndex, List children; private final boolean isFinalNode; @@ -145,14 +149,19 @@ private static class BuildNode { private final BuildNode root = new BuildNode(false); void insert(String filter) { - insertNode(filter, root); + insertNode(filter, root, 0); } FilterPath build() { return buildPath("", root); } - static void insertNode(String filter, BuildNode node) { + static void insertNode(String filter, BuildNode node, int depth) { + if (depth > MAX_TREE_DEPTH) { + throw new IllegalArgumentException( + "Filter exceeds maximum depth at [" + (filter.length() > 100 ? filter.substring(0, 100) : filter) + "]" + ); + } int end = filter.length(); int splitPosition = -1; boolean findEscapes = false; @@ -171,7 +180,7 @@ static void insertNode(String filter, BuildNode node) { String field = findEscapes ? filter.substring(0, splitPosition).replace("\\.", ".") : filter.substring(0, splitPosition); BuildNode child = node.children.computeIfAbsent(field, f -> new BuildNode(false)); if (false == child.isFinalNode) { - insertNode(filter.substring(splitPosition + 1), child); + insertNode(filter.substring(splitPosition + 1), child, depth + 1); } } else { String field = findEscapes ? filter.replace("\\.", ".") : filter; diff --git a/libs/x-content/src/test/java/org/elasticsearch/xcontent/support/filtering/FilterPathTests.java b/libs/x-content/src/test/java/org/elasticsearch/xcontent/support/filtering/FilterPathTests.java index abe2a78112fd7..5de8b5c1029e6 100644 --- a/libs/x-content/src/test/java/org/elasticsearch/xcontent/support/filtering/FilterPathTests.java +++ b/libs/x-content/src/test/java/org/elasticsearch/xcontent/support/filtering/FilterPathTests.java @@ -21,6 +21,7 @@ import static java.util.Collections.singleton; import static org.hamcrest.Matchers.arrayWithSize; +import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; public class FilterPathTests extends ESTestCase { @@ -403,4 +404,16 @@ public void testDotInFieldName() { assertTrue(filterPaths[0].matches("a.b.c.d", nextFilters, true)); assertEquals(nextFilters.size(), 0); } + + public void testDepthChecking() { + final String atLimit = "x" + (".x").repeat(FilterPath.MAX_TREE_DEPTH); + final String aboveLimit = atLimit + ".y"; + + var paths = FilterPath.compile(Set.of(atLimit)); + assertThat(paths, arrayWithSize(1)); + + var ex = expectThrows(IllegalArgumentException.class, () -> FilterPath.compile(Set.of(aboveLimit))); + assertThat(ex.getMessage(), containsString("maximum depth")); + assertThat(ex.getMessage(), containsString("[y]")); + } }