Skip to content

Commit f0948d3

Browse files
[7.17] Add maximum nested depth check to WKT parser (#111843) (#111876)
* [7.17] Add maximum nested depth check to WKT parser (#111843) Manually backported #111843 to 7.17. Since the original PR used Java language features not supported in Java8, and 7.17 requires Java8 language level support, some small changes needed to be made. * Update docs/changelog/111876.yaml * Delete docs/changelog/111876.yaml
1 parent 1d9bb3d commit f0948d3

File tree

3 files changed

+51
-7
lines changed

3 files changed

+51
-7
lines changed

docs/changelog/111843.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 111843
2+
summary: Add maximum nested depth check to WKT parser
3+
area: Geo
4+
type: bug
5+
issues: []

libs/geo/src/main/java/org/elasticsearch/geometry/utils/WellKnownText.java

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ public class WellKnownText {
4141
public static final String RPAREN = ")";
4242
public static final String COMMA = ",";
4343
public static final String NAN = "NaN";
44+
public static final int MAX_NESTED_DEPTH = 1000;
4445

4546
private static final String NUMBER = "<NUMBER>";
4647
private static final String EOF = "END-OF-STREAM";
@@ -233,7 +234,7 @@ public static Geometry fromWKT(GeometryValidator validator, boolean coerce, Stri
233234
tokenizer.whitespaceChars('\r', '\r');
234235
tokenizer.whitespaceChars('\n', '\n');
235236
tokenizer.commentChar('#');
236-
Geometry geometry = parseGeometry(tokenizer, coerce);
237+
Geometry geometry = parseGeometry(tokenizer, coerce, 0);
237238
validator.validate(geometry);
238239
return geometry;
239240
} finally {
@@ -244,7 +245,7 @@ public static Geometry fromWKT(GeometryValidator validator, boolean coerce, Stri
244245
/**
245246
* parse geometry from the stream tokenizer
246247
*/
247-
private static Geometry parseGeometry(StreamTokenizer stream, boolean coerce) throws IOException, ParseException {
248+
private static Geometry parseGeometry(StreamTokenizer stream, boolean coerce, int depth) throws IOException, ParseException {
248249
final String type = nextWord(stream).toLowerCase(Locale.ROOT);
249250
switch (type) {
250251
case "point":
@@ -262,22 +263,25 @@ private static Geometry parseGeometry(StreamTokenizer stream, boolean coerce) th
262263
case "bbox":
263264
return parseBBox(stream);
264265
case "geometrycollection":
265-
return parseGeometryCollection(stream, coerce);
266+
return parseGeometryCollection(stream, coerce, depth + 1);
266267
case "circle": // Not part of the standard, but we need it for internal serialization
267268
return parseCircle(stream);
268269
}
269270
throw new IllegalArgumentException("Unknown geometry type: " + type);
270271
}
271272

272-
private static GeometryCollection<Geometry> parseGeometryCollection(StreamTokenizer stream, boolean coerce) throws IOException,
273-
ParseException {
273+
private static GeometryCollection<Geometry> parseGeometryCollection(StreamTokenizer stream, boolean coerce, int depth)
274+
throws IOException, ParseException {
274275
if (nextEmptyOrOpen(stream).equals(EMPTY)) {
275276
return GeometryCollection.EMPTY;
276277
}
278+
if (depth > MAX_NESTED_DEPTH) {
279+
throw new ParseException("maximum nested depth of " + MAX_NESTED_DEPTH + " exceeded", stream.lineno());
280+
}
277281
List<Geometry> shapes = new ArrayList<>();
278-
shapes.add(parseGeometry(stream, coerce));
282+
shapes.add(parseGeometry(stream, coerce, depth));
279283
while (nextCloserOrComma(stream).equals(COMMA)) {
280-
shapes.add(parseGeometry(stream, coerce));
284+
shapes.add(parseGeometry(stream, coerce, depth));
281285
}
282286
return new GeometryCollection<>(shapes);
283287
}

libs/geo/src/test/java/org/elasticsearch/geometry/GeometryCollectionTests.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import java.util.Arrays;
2020
import java.util.Collections;
2121

22+
import static org.hamcrest.Matchers.containsString;
23+
2224
public class GeometryCollectionTests extends BaseGeometryTestCase<GeometryCollection<Geometry>> {
2325
@Override
2426
protected GeometryCollection<Geometry> createTestInstance(boolean hasAlt) {
@@ -64,4 +66,37 @@ public void testInitValidation() {
6466

6567
StandardValidator.instance(true).validate(new GeometryCollection<Geometry>(Collections.singletonList(new Point(20, 10, 30))));
6668
}
69+
70+
public void testDeeplyNestedCollection() throws IOException, ParseException {
71+
String wkt = makeDeeplyNestedGeometryCollectionWKT(WellKnownText.MAX_NESTED_DEPTH);
72+
Geometry parsed = WellKnownText.fromWKT(GeographyValidator.instance(true), true, wkt);
73+
assertEquals(WellKnownText.MAX_NESTED_DEPTH, countNestedGeometryCollections((GeometryCollection<?>) parsed));
74+
}
75+
76+
public void testTooDeeplyNestedCollection() {
77+
String wkt = makeDeeplyNestedGeometryCollectionWKT(WellKnownText.MAX_NESTED_DEPTH + 1);
78+
ParseException ex = expectThrows(ParseException.class, () -> WellKnownText.fromWKT(GeographyValidator.instance(true), true, wkt));
79+
assertThat(ex.getMessage(), containsString("maximum nested depth of " + WellKnownText.MAX_NESTED_DEPTH));
80+
}
81+
82+
private String makeDeeplyNestedGeometryCollectionWKT(int depth) {
83+
StringBuilder wkt = new StringBuilder();
84+
for (int i = 0; i < depth; i++) {
85+
wkt.append("GEOMETRYCOLLECTION (");
86+
}
87+
wkt.append("POINT (20.0 10.0)");
88+
for (int i = 0; i < depth; i++) {
89+
wkt.append(")");
90+
}
91+
return wkt.toString();
92+
}
93+
94+
private int countNestedGeometryCollections(GeometryCollection<?> geometry) {
95+
int count = 1;
96+
while (geometry.get(0) instanceof GeometryCollection<?>) {
97+
count += 1;
98+
geometry = (GeometryCollection<?>) geometry.get(0);
99+
}
100+
return count;
101+
}
67102
}

0 commit comments

Comments
 (0)