[geos-commits] [SCM] GEOS branch 3.14 updated. 47d87bfb2bcf6c427dfe2aa7425ee3f9fc2841c4

git at osgeo.org git at osgeo.org
Wed May 13 14:37:24 PDT 2026


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "GEOS".

The branch, 3.14 has been updated
       via  47d87bfb2bcf6c427dfe2aa7425ee3f9fc2841c4 (commit)
       via  e13c09ac45200f19a02b6e2262d6750b7cbd39a2 (commit)
       via  0f7182f24feff3e02440000a0f78cbbe41da1b98 (commit)
       via  bf706b77672c87a243d53fc7cb8133564336c0d5 (commit)
      from  03d50b54fd00017c74d34ba44a4378440a185061 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 47d87bfb2bcf6c427dfe2aa7425ee3f9fc2841c4
Author: Paul Ramsey <pramsey at cleverelephant.ca>
Date:   Wed May 13 13:57:19 2026 -0700

    MinGW64 CI build fix

diff --git a/include/geos/algorithm/distance/DistanceToPoint.h b/include/geos/algorithm/distance/DistanceToPoint.h
index 108b85506..f1889397b 100644
--- a/include/geos/algorithm/distance/DistanceToPoint.h
+++ b/include/geos/algorithm/distance/DistanceToPoint.h
@@ -19,6 +19,7 @@
 #pragma once
 
 #include <geos/geom/LineSegment.h> // for composition
+#include <geos/export.h>
 
 namespace geos {
 namespace algorithm {
@@ -43,7 +44,7 @@ namespace distance { // geos::algorithm::distance
  *
  * Also computes two points which are separated by the distance.
  */
-class DistanceToPoint {
+class GEOS_DLL DistanceToPoint {
 public:
 
     DistanceToPoint() {}

commit e13c09ac45200f19a02b6e2262d6750b7cbd39a2
Author: Paul Ramsey <pramsey at cleverelephant.ca>
Date:   Wed May 13 13:46:19 2026 -0700

    Fix CI issue for mingw64

diff --git a/capi/geos_ts_c.cpp b/capi/geos_ts_c.cpp
index 8802cff8f..78724b430 100644
--- a/capi/geos_ts_c.cpp
+++ b/capi/geos_ts_c.cpp
@@ -1824,6 +1824,10 @@ extern "C" {
     GEOSGridIntersectionFractions_r(GEOSContextHandle_t extHandle, const Geometry* g, double xmin, double ymin,
                                     double xmax, double ymax, unsigned nx, unsigned ny, float* buf)
     {
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Warray-bounds"
+#endif
         return execute(extHandle, 0, [&]() {
             Envelope env(xmin, xmax, ymin, ymax);
             double dx = env.getWidth() / static_cast<double>(nx);
@@ -1839,6 +1843,9 @@ extern "C" {
 
             return 1;
         });
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
     }
 
     Geometry*

commit 0f7182f24feff3e02440000a0f78cbbe41da1b98
Author: Paul Ramsey <pramsey at cleverelephant.ca>
Date:   Wed May 13 12:13:30 2026 -0700

    News entry for GH-1437

diff --git a/NEWS.md b/NEWS.md
index 08b2802a4..7e7ea284e 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -8,6 +8,7 @@
   - Fix some cases of dropped M values in overlay (GH-1364/GH-1388, Dan Baston)
   - GEOSClusterDBSCAN fix unassigned clusters with minPoints <= 1 (GH-1386, Dan Baston)
   - Fix crash in GEOSConvexHull (GH-1358, Dan Baston)
+  - Guard against stack overflow in inputs (GH-1437, Paul Ramsey)
 
 ## Changes in 3.14.1
 2025-10-27

commit bf706b77672c87a243d53fc7cb8133564336c0d5
Author: Paul Ramsey <pramsey at cleverelephant.ca>
Date:   Wed May 13 12:11:50 2026 -0700

    Limit recursion depth in WKT/WKB/GeoJSON readers
    
    Prevents stack overflow on malformed input.

diff --git a/capi/geos_ts_c.cpp b/capi/geos_ts_c.cpp
index f4690f04f..8802cff8f 100644
--- a/capi/geos_ts_c.cpp
+++ b/capi/geos_ts_c.cpp
@@ -1109,6 +1109,7 @@ extern "C" {
         return execute(extHandle, [&]() {
             auto ids = clusters->getClusterIds(GEOS_CLUSTER_NONE);
             std::size_t* ids_buf = (size_t*) malloc(ids.size() * sizeof(std::size_t));
+            if (!ids_buf) return ids_buf;
             std::copy(ids.begin(), ids.end(), ids_buf);
             return ids_buf;
         });
diff --git a/include/geos/io/GeoJSONReader.h b/include/geos/io/GeoJSONReader.h
index 41834f1dd..9d9d09d20 100644
--- a/include/geos/io/GeoJSONReader.h
+++ b/include/geos/io/GeoJSONReader.h
@@ -75,6 +75,7 @@ public:
 private:
 
     const geom::GeometryFactory& geometryFactory;
+    mutable int parseDepth_ = 0;
 
     std::unique_ptr<geom::Geometry> readFeatureForGeometry(const geos_nlohmann::json& j) const;
 
diff --git a/include/geos/io/WKBReader.h b/include/geos/io/WKBReader.h
index 9d410c250..2b8b5beae 100644
--- a/include/geos/io/WKBReader.h
+++ b/include/geos/io/WKBReader.h
@@ -146,6 +146,8 @@ private:
 
     std::array<double, 4> ordValues;
 
+    int parseDepth_ = 0;
+
     std::unique_ptr<geom::Geometry> readGeometry();
 
     std::unique_ptr<geom::Point> readPoint();
diff --git a/include/geos/io/WKTReader.h b/include/geos/io/WKTReader.h
index 7294d64f5..46529c3c8 100644
--- a/include/geos/io/WKTReader.h
+++ b/include/geos/io/WKTReader.h
@@ -148,6 +148,7 @@ private:
     const geom::GeometryFactory* geometryFactory;
     const geom::PrecisionModel* precisionModel;
     bool fixStructure;
+    mutable int parseDepth_ = 0;
 
     void getPreciseCoordinate(io::StringTokenizer* tokenizer, OrdinateSet& ordinateFlags, geom::CoordinateXYZM&) const;
 
diff --git a/src/io/GeoJSONReader.cpp b/src/io/GeoJSONReader.cpp
index 0f0817e32..1ab54dd9e 100644
--- a/src/io/GeoJSONReader.cpp
+++ b/src/io/GeoJSONReader.cpp
@@ -40,12 +40,15 @@ using json = geos_nlohmann::json;
 namespace geos {
 namespace io { // geos.io
 
+static constexpr int MAX_PARSE_DEPTH = 100;
+
 GeoJSONReader::GeoJSONReader(): GeoJSONReader(*(GeometryFactory::getDefaultInstance())) {}
 
 GeoJSONReader::GeoJSONReader(const geom::GeometryFactory& gf) : geometryFactory(gf) {}
 
 std::unique_ptr<geom::Geometry> GeoJSONReader::read(const std::string& geoJsonText) const
 {
+    parseDepth_ = 0;
     try {
         const json& j = json::parse(geoJsonText);
         const std::string& type = j.at("type");
@@ -66,6 +69,7 @@ std::unique_ptr<geom::Geometry> GeoJSONReader::read(const std::string& geoJsonTe
 
 GeoJSONFeatureCollection GeoJSONReader::readFeatures(const std::string& geoJsonText) const
 {
+    parseDepth_ = 0;
     try {
         const json& j = json::parse(geoJsonText);
         const std::string& type = j.at("type");
@@ -121,6 +125,12 @@ std::map<std::string, GeoJSONValue> GeoJSONReader::readProperties(
 GeoJSONValue GeoJSONReader::readProperty(
     const geos_nlohmann::json& value) const
 {
+    if (parseDepth_ >= MAX_PARSE_DEPTH) {
+        throw ParseException("Input property exceeds nesting depth limit");
+    }
+    ++parseDepth_;
+    struct DepthGuard { int& d; ~DepthGuard() { --d; } } guard{parseDepth_};
+
     if (value.is_string()) {
         return GeoJSONValue { value.get<std::string>() };
     }
@@ -179,6 +189,12 @@ GeoJSONFeatureCollection GeoJSONReader::readFeatureCollection(
 std::unique_ptr<geom::Geometry> GeoJSONReader::readGeometry(
     const geos_nlohmann::json& j) const
 {
+    if (parseDepth_ >= MAX_PARSE_DEPTH) {
+        throw ParseException("Input geometry exceeds nesting depth limit");
+    }
+    ++parseDepth_;
+    struct DepthGuard { int& d; ~DepthGuard() { --d; } } guard{parseDepth_};
+
     const std::string& type = j.at("type");
     if (type == "Point") {
         return readPoint(j);
diff --git a/src/io/WKBReader.cpp b/src/io/WKBReader.cpp
index 5410ab0c7..d81a88ddc 100644
--- a/src/io/WKBReader.cpp
+++ b/src/io/WKBReader.cpp
@@ -245,13 +245,22 @@ WKBReader::read(std::istream& is)
 std::unique_ptr<Geometry>
 WKBReader::read(const unsigned char* buf, size_t size)
 {
+    parseDepth_ = 0;
     dis = ByteOrderDataInStream(buf, size); // will default to machine endian
     return readGeometry();
 }
 
+static constexpr int MAX_PARSE_DEPTH = 100;
+
 std::unique_ptr<Geometry>
 WKBReader::readGeometry()
 {
+    if (parseDepth_ >= MAX_PARSE_DEPTH) {
+        throw ParseException("Input geometry exceeds nesting depth limit");
+    }
+    ++parseDepth_;
+    struct DepthGuard { int& d; ~DepthGuard() { --d; } } guard{parseDepth_};
+
     // determine byte order
     unsigned char byteOrder = dis.readByte();
 
diff --git a/src/io/WKTReader.cpp b/src/io/WKTReader.cpp
index 2b4400a6b..5fec339bb 100644
--- a/src/io/WKTReader.cpp
+++ b/src/io/WKTReader.cpp
@@ -49,9 +49,12 @@ using namespace geos::geom;
 namespace geos {
 namespace io { // geos.io
 
+static constexpr int MAX_PARSE_DEPTH = 100;
+
 std::unique_ptr<Geometry>
 WKTReader::read(const std::string& wellKnownText) const
 {
+    parseDepth_ = 0;
     CLocalizer clocale;
     StringTokenizer tokenizer(wellKnownText);
     OrdinateSet ordinateFlags = OrdinateSet::createXY();
@@ -285,6 +288,12 @@ WKTReader::getNextWord(StringTokenizer* tokenizer)
 std::unique_ptr<Geometry>
 WKTReader::readGeometryTaggedText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags, const GeometryTypeId* emptyType) const
 {
+    if (parseDepth_ >= MAX_PARSE_DEPTH) {
+        throw ParseException("Input geometry exceeds nesting depth limit");
+    }
+    ++parseDepth_;
+    struct DepthGuard { int& d; ~DepthGuard() { --d; } } guard{parseDepth_};
+
     std::string type = getNextWord(tokenizer);
 
     std::unique_ptr<Geometry> geom;
diff --git a/tests/unit/io/GeoJSONReaderTest.cpp b/tests/unit/io/GeoJSONReaderTest.cpp
index 2d474d3d7..c419eaf55 100644
--- a/tests/unit/io/GeoJSONReaderTest.cpp
+++ b/tests/unit/io/GeoJSONReaderTest.cpp
@@ -619,4 +619,21 @@ void object::test<40>
     ensure_equals(static_cast<size_t>(geom->getCoordinateDimension()), 3u);
 }
 
+// Deeply nested GeometryCollection should throw ParseException (not stack overflow)
+template<>
+template<>
+void object::test<41>
+()
+{
+    std::string geojson;
+    for (int i = 0; i < 200; i++) geojson += "{\"type\":\"GeometryCollection\",\"geometries\":[";
+    geojson += "{\"type\":\"Point\",\"coordinates\":[0,0]}";
+    for (int i = 0; i < 200; i++) geojson += "]}";
+
+    try {
+        geojsonreader.read(geojson);
+        fail("Expected ParseException for deeply nested GeoJSON");
+    } catch (const geos::io::ParseException&) {}
+}
+
 }
diff --git a/tests/unit/io/WKBReaderTest.cpp b/tests/unit/io/WKBReaderTest.cpp
index 6628351a8..ebdba7a40 100644
--- a/tests/unit/io/WKBReaderTest.cpp
+++ b/tests/unit/io/WKBReaderTest.cpp
@@ -7,11 +7,13 @@
 
 // tut
 #include <tut/tut.hpp>
+#include <tut/tut_macros.hpp>
 // geos
 #include <geos/io/WKBReader.h>
 #include <geos/io/WKBConstants.h>
 #include <geos/io/WKBWriter.h>
 #include <geos/io/WKTReader.h>
+#include <geos/io/ParseException.h>
 #include <geos/geom/CompoundCurve.h>
 #include <geos/geom/CurvePolygon.h>
 #include <geos/geom/PrecisionModel.h>
@@ -838,5 +840,29 @@ void object::test<36>
                    "ParseException: Expected SimpleCurve but got Point");
 }
 
+
+template<>
+template<>
+void object::test<37>
+()
+{
+    set_test_name("ParseException on deeply nested WKB collection avoid stack overflow");
+
+    // Each GeometryCollection header (NDR): byteOrder=01, type=07000000, numGeoms=01000000
+    const std::vector<unsigned char> header = {0x01, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00};
+    // Innermost empty GeometryCollection: byteOrder=01, type=07000000, numGeoms=00000000
+    const std::vector<unsigned char> inner = {0x01, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+    std::vector<unsigned char> buf;
+    for (int i = 0; i < 200; i++) buf.insert(buf.end(), header.begin(), header.end());
+    buf.insert(buf.end(), inner.begin(), inner.end());
+
+    ensure_THROW(wkbreader.read(buf.data(), buf.size()), geos::io::ParseException);
+
+    // try {
+    //     wkbreader.read(buf.data(), buf.size());
+    //     fail("Expected ParseException for deeply nested WKB");
+    // } catch (const geos::util::GEOSException&) {}
+}
+
 } // namespace tut
 
diff --git a/tests/unit/io/WKTReaderTest.cpp b/tests/unit/io/WKTReaderTest.cpp
index af9621916..2a28e805b 100644
--- a/tests/unit/io/WKTReaderTest.cpp
+++ b/tests/unit/io/WKTReaderTest.cpp
@@ -3,6 +3,7 @@
 
 // tut
 #include <tut/tut.hpp>
+#include <tut/tut_macros.hpp>
 // geos
 #include <geos/io/WKTReader.h>
 #include <geos/io/WKTWriter.h>
@@ -162,17 +163,7 @@ template<>
 void object::test<6>
 ()
 {
-    try {
-        wktreader.read("POLYGON( EMPTY, (1 1,2 2,1 2,1 1))");
-        fail("Did not get expected exception");
-    }
-    catch(const geos::util::IllegalArgumentException& ex) {
-        ensure("Got expected exception", true);
-        (void)(ex.what());
-    }
-    catch(...) {
-        fail("Got unexpected exception");
-    }
+    ensure_THROW(wktreader.read("POLYGON( EMPTY, (1 1,2 2,1 2,1 1))"), geos::util::IllegalArgumentException);
 }
 
 // POINT(0 0) http://trac.osgeo.org/geos/ticket/610
@@ -499,4 +490,17 @@ void object::test<25>
     ensure_parseexception("POLYGON Z M EMPTY");
 }
 
+template<>
+template<>
+void object::test<26>
+()
+{
+    set_test_name("ParseException on deeply nested WKT collection avoid stack overflow");
+    std::string wkt;
+    for (int i = 0; i < 200; i++) wkt += "GEOMETRYCOLLECTION(";
+    wkt += "POINT(0 0)";
+    for (int i = 0; i < 200; i++) wkt += ")";
+    ensure_parseexception(wkt);
+}
+
 } // namespace tut

-----------------------------------------------------------------------

Summary of changes:
 NEWS.md                                           |  1 +
 capi/geos_ts_c.cpp                                |  8 +++++++
 include/geos/algorithm/distance/DistanceToPoint.h |  3 ++-
 include/geos/io/GeoJSONReader.h                   |  1 +
 include/geos/io/WKBReader.h                       |  2 ++
 include/geos/io/WKTReader.h                       |  1 +
 src/io/GeoJSONReader.cpp                          | 16 ++++++++++++++
 src/io/WKBReader.cpp                              |  9 ++++++++
 src/io/WKTReader.cpp                              |  9 ++++++++
 tests/unit/io/GeoJSONReaderTest.cpp               | 17 +++++++++++++++
 tests/unit/io/WKBReaderTest.cpp                   | 26 +++++++++++++++++++++++
 tests/unit/io/WKTReaderTest.cpp                   | 26 +++++++++++++----------
 12 files changed, 107 insertions(+), 12 deletions(-)


hooks/post-receive
-- 
GEOS


More information about the geos-commits mailing list