[geos-commits] [SCM] GEOS branch main updated. 8b8b3da7a3d9fb8953ff60bc49aa0320d51ae45c
git at osgeo.org
git at osgeo.org
Wed May 13 12:12:56 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, main has been updated
via 8b8b3da7a3d9fb8953ff60bc49aa0320d51ae45c (commit)
from cf859843556dd76e9597c588543915b7aa7d7bc4 (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 8b8b3da7a3d9fb8953ff60bc49aa0320d51ae45c
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 a1be8e65b..9488b4be9 100644
--- a/capi/geos_ts_c.cpp
+++ b/capi/geos_ts_c.cpp
@@ -1147,6 +1147,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 58eeb8f74..3b97cb498 100644
--- a/include/geos/io/WKBReader.h
+++ b/include/geos/io/WKBReader.h
@@ -156,6 +156,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 9fc86c978..bd4bc56cd 100644
--- a/include/geos/io/WKTReader.h
+++ b/include/geos/io/WKTReader.h
@@ -149,6 +149,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 65ba6c4c1..d5683f2dc 100644
--- a/src/io/WKBReader.cpp
+++ b/src/io/WKBReader.cpp
@@ -252,13 +252,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 3c1d6fa69..a49b2cf98 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();
@@ -302,6 +305,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:
capi/geos_ts_c.cpp | 1 +
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 +++++++++++++++-----------
10 files changed, 97 insertions(+), 11 deletions(-)
hooks/post-receive
--
GEOS
More information about the geos-commits
mailing list