VYPR
Moderate severityNVD Advisory· Published Apr 8, 2025· Updated Apr 8, 2025

CVE-2024-52981

CVE-2024-52981

Description

An issue was discovered in Elasticsearch, where a large recursion using the Well-KnownText formatted string with nested GeometryCollection objects could cause a stackoverflow.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
org.elasticsearch:elasticsearchMaven
>= 7.17.0, < 7.17.247.17.24
org.elasticsearch:elasticsearchMaven
>= 8.0.0-alpha1, < 8.15.18.15.1

Affected products

1

Patches

3
f0948d38fdc8

[7.17] Add maximum nested depth check to WKT parser (#111843) (#111876)

https://github.com/elastic/elasticsearchCraig TavernerAug 15, 2024via ghsa
3 files changed · +51 7
  • docs/changelog/111843.yaml+5 0 added
    @@ -0,0 +1,5 @@
    +pr: 111843
    +summary: Add maximum nested depth check to WKT parser
    +area: Geo
    +type: bug
    +issues: []
    
  • libs/geo/src/main/java/org/elasticsearch/geometry/utils/WellKnownText.java+11 7 modified
    @@ -41,6 +41,7 @@ public class WellKnownText {
         public static final String RPAREN = ")";
         public static final String COMMA = ",";
         public static final String NAN = "NaN";
    +    public static final int MAX_NESTED_DEPTH = 1000;
     
         private static final String NUMBER = "<NUMBER>";
         private static final String EOF = "END-OF-STREAM";
    @@ -233,7 +234,7 @@ public static Geometry fromWKT(GeometryValidator validator, boolean coerce, Stri
                 tokenizer.whitespaceChars('\r', '\r');
                 tokenizer.whitespaceChars('\n', '\n');
                 tokenizer.commentChar('#');
    -            Geometry geometry = parseGeometry(tokenizer, coerce);
    +            Geometry geometry = parseGeometry(tokenizer, coerce, 0);
                 validator.validate(geometry);
                 return geometry;
             } finally {
    @@ -244,7 +245,7 @@ public static Geometry fromWKT(GeometryValidator validator, boolean coerce, Stri
         /**
          * parse geometry from the stream tokenizer
          */
    -    private static Geometry parseGeometry(StreamTokenizer stream, boolean coerce) throws IOException, ParseException {
    +    private static Geometry parseGeometry(StreamTokenizer stream, boolean coerce, int depth) throws IOException, ParseException {
             final String type = nextWord(stream).toLowerCase(Locale.ROOT);
             switch (type) {
                 case "point":
    @@ -262,22 +263,25 @@ private static Geometry parseGeometry(StreamTokenizer stream, boolean coerce) th
                 case "bbox":
                     return parseBBox(stream);
                 case "geometrycollection":
    -                return parseGeometryCollection(stream, coerce);
    +                return parseGeometryCollection(stream, coerce, depth + 1);
                 case "circle": // Not part of the standard, but we need it for internal serialization
                     return parseCircle(stream);
             }
             throw new IllegalArgumentException("Unknown geometry type: " + type);
         }
     
    -    private static GeometryCollection<Geometry> parseGeometryCollection(StreamTokenizer stream, boolean coerce) throws IOException,
    -        ParseException {
    +    private static GeometryCollection<Geometry> parseGeometryCollection(StreamTokenizer stream, boolean coerce, int depth)
    +        throws IOException, ParseException {
             if (nextEmptyOrOpen(stream).equals(EMPTY)) {
                 return GeometryCollection.EMPTY;
             }
    +        if (depth > MAX_NESTED_DEPTH) {
    +            throw new ParseException("maximum nested depth of " + MAX_NESTED_DEPTH + " exceeded", stream.lineno());
    +        }
             List<Geometry> shapes = new ArrayList<>();
    -        shapes.add(parseGeometry(stream, coerce));
    +        shapes.add(parseGeometry(stream, coerce, depth));
             while (nextCloserOrComma(stream).equals(COMMA)) {
    -            shapes.add(parseGeometry(stream, coerce));
    +            shapes.add(parseGeometry(stream, coerce, depth));
             }
             return new GeometryCollection<>(shapes);
         }
    
  • libs/geo/src/test/java/org/elasticsearch/geometry/GeometryCollectionTests.java+35 0 modified
    @@ -19,6 +19,8 @@
     import java.util.Arrays;
     import java.util.Collections;
     
    +import static org.hamcrest.Matchers.containsString;
    +
     public class GeometryCollectionTests extends BaseGeometryTestCase<GeometryCollection<Geometry>> {
         @Override
         protected GeometryCollection<Geometry> createTestInstance(boolean hasAlt) {
    @@ -64,4 +66,37 @@ public void testInitValidation() {
     
             StandardValidator.instance(true).validate(new GeometryCollection<Geometry>(Collections.singletonList(new Point(20, 10, 30))));
         }
    +
    +    public void testDeeplyNestedCollection() throws IOException, ParseException {
    +        String wkt = makeDeeplyNestedGeometryCollectionWKT(WellKnownText.MAX_NESTED_DEPTH);
    +        Geometry parsed = WellKnownText.fromWKT(GeographyValidator.instance(true), true, wkt);
    +        assertEquals(WellKnownText.MAX_NESTED_DEPTH, countNestedGeometryCollections((GeometryCollection<?>) parsed));
    +    }
    +
    +    public void testTooDeeplyNestedCollection() {
    +        String wkt = makeDeeplyNestedGeometryCollectionWKT(WellKnownText.MAX_NESTED_DEPTH + 1);
    +        ParseException ex = expectThrows(ParseException.class, () -> WellKnownText.fromWKT(GeographyValidator.instance(true), true, wkt));
    +        assertThat(ex.getMessage(), containsString("maximum nested depth of " + WellKnownText.MAX_NESTED_DEPTH));
    +    }
    +
    +    private String makeDeeplyNestedGeometryCollectionWKT(int depth) {
    +        StringBuilder wkt = new StringBuilder();
    +        for (int i = 0; i < depth; i++) {
    +            wkt.append("GEOMETRYCOLLECTION (");
    +        }
    +        wkt.append("POINT (20.0 10.0)");
    +        for (int i = 0; i < depth; i++) {
    +            wkt.append(")");
    +        }
    +        return wkt.toString();
    +    }
    +
    +    private int countNestedGeometryCollections(GeometryCollection<?> geometry) {
    +        int count = 1;
    +        while (geometry.get(0) instanceof GeometryCollection<?>) {
    +            count += 1;
    +            geometry = (GeometryCollection<?>) geometry.get(0);
    +        }
    +        return count;
    +    }
     }
    
91ddb124219a

Add maximum nested depth check to WKT parser (#111843) (#111849)

https://github.com/elastic/elasticsearchCraig TavernerAug 13, 2024via ghsa
3 files changed · +57 29
  • docs/changelog/111843.yaml+5 0 added
    @@ -0,0 +1,5 @@
    +pr: 111843
    +summary: Add maximum nested depth check to WKT parser
    +area: Geo
    +type: bug
    +issues: []
    
  • libs/geo/src/main/java/org/elasticsearch/geometry/utils/WellKnownText.java+25 29 modified
    @@ -43,6 +43,7 @@ public class WellKnownText {
         public static final String RPAREN = ")";
         public static final String COMMA = ",";
         public static final String NAN = "NaN";
    +    public static final int MAX_NESTED_DEPTH = 1000;
     
         private static final String NUMBER = "<NUMBER>";
         private static final String EOF = "END-OF-STREAM";
    @@ -425,7 +426,7 @@ public static Geometry fromWKT(GeometryValidator validator, boolean coerce, Stri
                 tokenizer.whitespaceChars('\r', '\r');
                 tokenizer.whitespaceChars('\n', '\n');
                 tokenizer.commentChar('#');
    -            Geometry geometry = parseGeometry(tokenizer, coerce);
    +            Geometry geometry = parseGeometry(tokenizer, coerce, 0);
                 validator.validate(geometry);
                 return geometry;
             } finally {
    @@ -436,40 +437,35 @@ public static Geometry fromWKT(GeometryValidator validator, boolean coerce, Stri
         /**
          * parse geometry from the stream tokenizer
          */
    -    private static Geometry parseGeometry(StreamTokenizer stream, boolean coerce) throws IOException, ParseException {
    +    private static Geometry parseGeometry(StreamTokenizer stream, boolean coerce, int depth) throws IOException, ParseException {
             final String type = nextWord(stream).toLowerCase(Locale.ROOT);
    -        switch (type) {
    -            case "point":
    -                return parsePoint(stream);
    -            case "multipoint":
    -                return parseMultiPoint(stream);
    -            case "linestring":
    -                return parseLine(stream);
    -            case "multilinestring":
    -                return parseMultiLine(stream);
    -            case "polygon":
    -                return parsePolygon(stream, coerce);
    -            case "multipolygon":
    -                return parseMultiPolygon(stream, coerce);
    -            case "bbox":
    -                return parseBBox(stream);
    -            case "geometrycollection":
    -                return parseGeometryCollection(stream, coerce);
    -            case "circle": // Not part of the standard, but we need it for internal serialization
    -                return parseCircle(stream);
    -        }
    -        throw new IllegalArgumentException("Unknown geometry type: " + type);
    -    }
    -
    -    private static GeometryCollection<Geometry> parseGeometryCollection(StreamTokenizer stream, boolean coerce) throws IOException,
    -        ParseException {
    +        return switch (type) {
    +            case "point" -> parsePoint(stream);
    +            case "multipoint" -> parseMultiPoint(stream);
    +            case "linestring" -> parseLine(stream);
    +            case "multilinestring" -> parseMultiLine(stream);
    +            case "polygon" -> parsePolygon(stream, coerce);
    +            case "multipolygon" -> parseMultiPolygon(stream, coerce);
    +            case "bbox" -> parseBBox(stream);
    +            case "geometrycollection" -> parseGeometryCollection(stream, coerce, depth + 1);
    +            case "circle" -> // Not part of the standard, but we need it for internal serialization
    +                parseCircle(stream);
    +            default -> throw new IllegalArgumentException("Unknown geometry type: " + type);
    +        };
    +    }
    +
    +    private static GeometryCollection<Geometry> parseGeometryCollection(StreamTokenizer stream, boolean coerce, int depth)
    +        throws IOException, ParseException {
             if (nextEmptyOrOpen(stream).equals(EMPTY)) {
                 return GeometryCollection.EMPTY;
             }
    +        if (depth > MAX_NESTED_DEPTH) {
    +            throw new ParseException("maximum nested depth of " + MAX_NESTED_DEPTH + " exceeded", stream.lineno());
    +        }
             List<Geometry> shapes = new ArrayList<>();
    -        shapes.add(parseGeometry(stream, coerce));
    +        shapes.add(parseGeometry(stream, coerce, depth));
             while (nextCloserOrComma(stream).equals(COMMA)) {
    -            shapes.add(parseGeometry(stream, coerce));
    +            shapes.add(parseGeometry(stream, coerce, depth));
             }
             return new GeometryCollection<>(shapes);
         }
    
  • libs/geo/src/test/java/org/elasticsearch/geometry/GeometryCollectionTests.java+27 0 modified
    @@ -19,6 +19,8 @@
     import java.util.Arrays;
     import java.util.Collections;
     
    +import static org.hamcrest.Matchers.containsString;
    +
     public class GeometryCollectionTests extends BaseGeometryTestCase<GeometryCollection<Geometry>> {
         @Override
         protected GeometryCollection<Geometry> createTestInstance(boolean hasAlt) {
    @@ -65,6 +67,31 @@ public void testInitValidation() {
             StandardValidator.instance(true).validate(new GeometryCollection<Geometry>(Collections.singletonList(new Point(20, 10, 30))));
         }
     
    +    public void testDeeplyNestedCollection() throws IOException, ParseException {
    +        String wkt = makeDeeplyNestedGeometryCollectionWKT(WellKnownText.MAX_NESTED_DEPTH);
    +        Geometry parsed = WellKnownText.fromWKT(GeographyValidator.instance(true), true, wkt);
    +        assertEquals(WellKnownText.MAX_NESTED_DEPTH, countNestedGeometryCollections((GeometryCollection<?>) parsed));
    +    }
    +
    +    public void testTooDeeplyNestedCollection() {
    +        String wkt = makeDeeplyNestedGeometryCollectionWKT(WellKnownText.MAX_NESTED_DEPTH + 1);
    +        ParseException ex = expectThrows(ParseException.class, () -> WellKnownText.fromWKT(GeographyValidator.instance(true), true, wkt));
    +        assertThat(ex.getMessage(), containsString("maximum nested depth of " + WellKnownText.MAX_NESTED_DEPTH));
    +    }
    +
    +    private String makeDeeplyNestedGeometryCollectionWKT(int depth) {
    +        return "GEOMETRYCOLLECTION (".repeat(depth) + "POINT (20.0 10.0)" + ")".repeat(depth);
    +    }
    +
    +    private int countNestedGeometryCollections(GeometryCollection<?> geometry) {
    +        int count = 1;
    +        while (geometry.get(0) instanceof GeometryCollection<?> g) {
    +            count += 1;
    +            geometry = g;
    +        }
    +        return count;
    +    }
    +
         @Override
         protected GeometryCollection<Geometry> mutateInstance(GeometryCollection<Geometry> instance) {
             return null;// TODO implement https://github.com/elastic/elasticsearch/issues/25929
    
097fc0654f93

Add maximum nested depth check to WKT parser (#111843)

https://github.com/elastic/elasticsearchCraig TavernerAug 13, 2024via ghsa
3 files changed · +57 29
  • docs/changelog/111843.yaml+5 0 added
    @@ -0,0 +1,5 @@
    +pr: 111843
    +summary: Add maximum nested depth check to WKT parser
    +area: Geo
    +type: bug
    +issues: []
    
  • libs/geo/src/main/java/org/elasticsearch/geometry/utils/WellKnownText.java+25 29 modified
    @@ -43,6 +43,7 @@ public class WellKnownText {
         public static final String RPAREN = ")";
         public static final String COMMA = ",";
         public static final String NAN = "NaN";
    +    public static final int MAX_NESTED_DEPTH = 1000;
     
         private static final String NUMBER = "<NUMBER>";
         private static final String EOF = "END-OF-STREAM";
    @@ -425,7 +426,7 @@ public static Geometry fromWKT(GeometryValidator validator, boolean coerce, Stri
                 tokenizer.whitespaceChars('\r', '\r');
                 tokenizer.whitespaceChars('\n', '\n');
                 tokenizer.commentChar('#');
    -            Geometry geometry = parseGeometry(tokenizer, coerce);
    +            Geometry geometry = parseGeometry(tokenizer, coerce, 0);
                 validator.validate(geometry);
                 return geometry;
             } finally {
    @@ -436,40 +437,35 @@ public static Geometry fromWKT(GeometryValidator validator, boolean coerce, Stri
         /**
          * parse geometry from the stream tokenizer
          */
    -    private static Geometry parseGeometry(StreamTokenizer stream, boolean coerce) throws IOException, ParseException {
    +    private static Geometry parseGeometry(StreamTokenizer stream, boolean coerce, int depth) throws IOException, ParseException {
             final String type = nextWord(stream).toLowerCase(Locale.ROOT);
    -        switch (type) {
    -            case "point":
    -                return parsePoint(stream);
    -            case "multipoint":
    -                return parseMultiPoint(stream);
    -            case "linestring":
    -                return parseLine(stream);
    -            case "multilinestring":
    -                return parseMultiLine(stream);
    -            case "polygon":
    -                return parsePolygon(stream, coerce);
    -            case "multipolygon":
    -                return parseMultiPolygon(stream, coerce);
    -            case "bbox":
    -                return parseBBox(stream);
    -            case "geometrycollection":
    -                return parseGeometryCollection(stream, coerce);
    -            case "circle": // Not part of the standard, but we need it for internal serialization
    -                return parseCircle(stream);
    -        }
    -        throw new IllegalArgumentException("Unknown geometry type: " + type);
    -    }
    -
    -    private static GeometryCollection<Geometry> parseGeometryCollection(StreamTokenizer stream, boolean coerce) throws IOException,
    -        ParseException {
    +        return switch (type) {
    +            case "point" -> parsePoint(stream);
    +            case "multipoint" -> parseMultiPoint(stream);
    +            case "linestring" -> parseLine(stream);
    +            case "multilinestring" -> parseMultiLine(stream);
    +            case "polygon" -> parsePolygon(stream, coerce);
    +            case "multipolygon" -> parseMultiPolygon(stream, coerce);
    +            case "bbox" -> parseBBox(stream);
    +            case "geometrycollection" -> parseGeometryCollection(stream, coerce, depth + 1);
    +            case "circle" -> // Not part of the standard, but we need it for internal serialization
    +                parseCircle(stream);
    +            default -> throw new IllegalArgumentException("Unknown geometry type: " + type);
    +        };
    +    }
    +
    +    private static GeometryCollection<Geometry> parseGeometryCollection(StreamTokenizer stream, boolean coerce, int depth)
    +        throws IOException, ParseException {
             if (nextEmptyOrOpen(stream).equals(EMPTY)) {
                 return GeometryCollection.EMPTY;
             }
    +        if (depth > MAX_NESTED_DEPTH) {
    +            throw new ParseException("maximum nested depth of " + MAX_NESTED_DEPTH + " exceeded", stream.lineno());
    +        }
             List<Geometry> shapes = new ArrayList<>();
    -        shapes.add(parseGeometry(stream, coerce));
    +        shapes.add(parseGeometry(stream, coerce, depth));
             while (nextCloserOrComma(stream).equals(COMMA)) {
    -            shapes.add(parseGeometry(stream, coerce));
    +            shapes.add(parseGeometry(stream, coerce, depth));
             }
             return new GeometryCollection<>(shapes);
         }
    
  • libs/geo/src/test/java/org/elasticsearch/geometry/GeometryCollectionTests.java+27 0 modified
    @@ -19,6 +19,8 @@
     import java.util.Arrays;
     import java.util.Collections;
     
    +import static org.hamcrest.Matchers.containsString;
    +
     public class GeometryCollectionTests extends BaseGeometryTestCase<GeometryCollection<Geometry>> {
         @Override
         protected GeometryCollection<Geometry> createTestInstance(boolean hasAlt) {
    @@ -65,6 +67,31 @@ public void testInitValidation() {
             StandardValidator.instance(true).validate(new GeometryCollection<Geometry>(Collections.singletonList(new Point(20, 10, 30))));
         }
     
    +    public void testDeeplyNestedCollection() throws IOException, ParseException {
    +        String wkt = makeDeeplyNestedGeometryCollectionWKT(WellKnownText.MAX_NESTED_DEPTH);
    +        Geometry parsed = WellKnownText.fromWKT(GeographyValidator.instance(true), true, wkt);
    +        assertEquals(WellKnownText.MAX_NESTED_DEPTH, countNestedGeometryCollections((GeometryCollection<?>) parsed));
    +    }
    +
    +    public void testTooDeeplyNestedCollection() {
    +        String wkt = makeDeeplyNestedGeometryCollectionWKT(WellKnownText.MAX_NESTED_DEPTH + 1);
    +        ParseException ex = expectThrows(ParseException.class, () -> WellKnownText.fromWKT(GeographyValidator.instance(true), true, wkt));
    +        assertThat(ex.getMessage(), containsString("maximum nested depth of " + WellKnownText.MAX_NESTED_DEPTH));
    +    }
    +
    +    private String makeDeeplyNestedGeometryCollectionWKT(int depth) {
    +        return "GEOMETRYCOLLECTION (".repeat(depth) + "POINT (20.0 10.0)" + ")".repeat(depth);
    +    }
    +
    +    private int countNestedGeometryCollections(GeometryCollection<?> geometry) {
    +        int count = 1;
    +        while (geometry.get(0) instanceof GeometryCollection<?> g) {
    +            count += 1;
    +            geometry = g;
    +        }
    +        return count;
    +    }
    +
         @Override
         protected GeometryCollection<Geometry> mutateInstance(GeometryCollection<Geometry> instance) {
             return null;// TODO implement https://github.com/elastic/elasticsearch/issues/25929
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

6

News mentions

0

No linked articles in our index yet.