[SCM] PostGIS branch master updated. 3.7.0beta2-19-g2b660395d
git at osgeo.org
git at osgeo.org
Mon Aug 10 15:01:50 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 "PostGIS".
The branch, master has been updated
via 2b660395d68955fff632ec1774d2f04698edb6d2 (commit)
via 4434b8dee7b7dc5a61af35251ff7c3cad3ad6fb1 (commit)
from 46203ec75913d646be780030819af6a6578d6718 (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 2b660395d68955fff632ec1774d2f04698edb6d2
Merge: 46203ec75 4434b8dee
Author: Darafei Praliaskouski <komzpa at gmail.com>
Date: Mon Aug 10 15:01:49 2026 -0700
Merge pull request 'liblwgeom: reject malformed GSERIALIZED polygon rings' (!724) from Komzpa/postgis:fix/ossfuzz-544800490-gserialized2-poly into master
Reject non-empty polygon rings with fewer than four points while deserializing v1/v2 GSERIALIZED payloads. This prevents malformed bytea inputs from constructing an LWPOLY that later crashes in gserialized2_from_lwpoly during fuzzer roundtrip serialization.
Credit to OSS-Fuzz.
OSS-Fuzz issue: https://issues.oss-fuzz.com/issues/544800490
Validation:
- git clang-format upstream/master
- make -C liblwgeom -j$(nproc) && make -C liblwgeom/cunit check
- UBSAN_OPTIONS=exitcode=77:silence_unsigned_overflow=1 gserialized_from_bytea_fuzzer testcase 5672880541859840
- make -C fuzzers dummyfuzzers FUZZER_OUT=/tmp/postgis-ossfuzz-544800490-fuzzout-rebased built gserialized_from_bytea_fuzzer, then stopped later at raster_deserialize_fuzzer because this local checkout was configured without raster_config.h
Reviewed-on: https://gitea.osgeo.org/postgis/postgis/pulls/724
commit 4434b8dee7b7dc5a61af35251ff7c3cad3ad6fb1
Author: Darafei Praliaskouski <me at komzpa.net>
Date: Tue Aug 11 01:54:59 2026 +0400
fix(liblwgeom): reject malformed GSERIALIZED polygon rings
Validate non-empty polygon ring point counts while deserializing GSERIALIZED buffers so malformed inputs cannot build LW polygons with too few ring points and crash during later serialization.
Credit to OSS-Fuzz.
OSS-Fuzz issue: https://issues.oss-fuzz.com/issues/544800490
diff --git a/NEWS b/NEWS
index 59a5c27c8..46206a39f 100644
--- a/NEWS
+++ b/NEWS
@@ -10,6 +10,8 @@ These are only changes since 3.7.0beta2.
- OSSFuzz 5607611131822080, keep GSERIALIZED bytea fuzzer allocations
consistent with hostile varlena size headers (Darafei Praliaskouski)
+ - OSSFuzz 544800490, reject malformed GSERIALIZED polygon rings before
+ deserializing geometry data (Darafei Praliaskouski)
- OSSFuzz 6152109301760000, reject overlong encoded polyline coordinate
varints (Darafei Praliaskouski)
diff --git a/liblwgeom/cunit/cu_gserialized1.c b/liblwgeom/cunit/cu_gserialized1.c
index a4ae77056..cfc49bcc7 100644
--- a/liblwgeom/cunit/cu_gserialized1.c
+++ b/liblwgeom/cunit/cu_gserialized1.c
@@ -1251,6 +1251,43 @@ assert_gserialized1_malformed_rejected(GSERIALIZED *g)
CU_ASSERT_EQUAL(gserialized1_peek_first_point(g, &point), LW_FAILURE);
}
+static uint8_t *
+gserialized1_test_payload_p(GSERIALIZED *g)
+{
+ return (uint8_t *)(void *)g + gserialized1_header_size(g);
+}
+
+static void
+test_gserialized1_malformed_polygon_ring_count(void)
+{
+ LWGEOM *lwgeom = lwgeom_from_wkt("POLYGON((0 0, 1 0, 1 1, 0 0))", LW_PARSER_CHECK_NONE);
+ LWGEOM *malformed_lwgeom;
+ GSERIALIZED *g;
+ uint8_t *payload;
+ uint32_t type;
+ uint32_t nrings;
+ uint32_t npoints = 1;
+
+ CU_ASSERT_PTR_NOT_NULL_FATAL(lwgeom);
+ g = gserialized1_from_lwgeom(lwgeom, NULL);
+ CU_ASSERT_PTR_NOT_NULL_FATAL(g);
+
+ payload = gserialized1_test_payload_p(g);
+ memcpy(&type, payload, sizeof(type));
+ memcpy(&nrings, payload + sizeof(uint32_t), sizeof(nrings));
+ CU_ASSERT_EQUAL(type, POLYGONTYPE);
+ CU_ASSERT_EQUAL(nrings, 1);
+ memcpy(payload + 2 * sizeof(uint32_t), &npoints, sizeof(npoints));
+
+ cu_error_msg_reset();
+ malformed_lwgeom = lwgeom_from_gserialized1(g);
+ CU_ASSERT_PTR_NULL(malformed_lwgeom);
+ CU_ASSERT_NOT_EQUAL(strlen(cu_error_msg), 0);
+
+ lwgeom_free(lwgeom);
+ lwfree(g);
+}
+
static void
test_gserialized1_malformed_declared_size(void)
{
@@ -1305,5 +1342,6 @@ void gserialized1_suite_setup(void)
PG_ADD_TEST(suite, test_gbox_same_2d);
PG_ADD_TEST(suite, test_signum_macro);
PG_ADD_TEST(suite, test_gserialized1_peek_first_point);
+ PG_ADD_TEST(suite, test_gserialized1_malformed_polygon_ring_count);
PG_ADD_TEST(suite, test_gserialized1_malformed_declared_size);
}
diff --git a/liblwgeom/cunit/cu_gserialized2.c b/liblwgeom/cunit/cu_gserialized2.c
index c60b0f56b..61cdf5a80 100644
--- a/liblwgeom/cunit/cu_gserialized2.c
+++ b/liblwgeom/cunit/cu_gserialized2.c
@@ -589,6 +589,37 @@ test_gserialized2_malformed_nurbs_degree(void)
lwfree(g);
}
+static void
+test_gserialized2_malformed_polygon_ring_count(void)
+{
+ LWGEOM *lwgeom = lwgeom_from_wkt("POLYGON((0 0, 1 0, 1 1, 0 0))", LW_PARSER_CHECK_NONE);
+ LWGEOM *malformed_lwgeom;
+ GSERIALIZED *g;
+ uint8_t *payload;
+ uint32_t type;
+ uint32_t nrings;
+ uint32_t npoints = 1;
+
+ CU_ASSERT_PTR_NOT_NULL_FATAL(lwgeom);
+ g = gserialized2_from_lwgeom(lwgeom, NULL);
+ CU_ASSERT_PTR_NOT_NULL_FATAL(g);
+
+ payload = gserialized2_test_payload_p(g);
+ memcpy(&type, payload, sizeof(type));
+ memcpy(&nrings, payload + sizeof(uint32_t), sizeof(nrings));
+ CU_ASSERT_EQUAL(type, POLYGONTYPE);
+ CU_ASSERT_EQUAL(nrings, 1);
+ memcpy(payload + 2 * sizeof(uint32_t), &npoints, sizeof(npoints));
+
+ cu_error_msg_reset();
+ malformed_lwgeom = lwgeom_from_gserialized2(g);
+ CU_ASSERT_PTR_NULL(malformed_lwgeom);
+ CU_ASSERT_NOT_EQUAL(strlen(cu_error_msg), 0);
+
+ lwgeom_free(lwgeom);
+ lwfree(g);
+}
+
static void
assert_gserialized2_malformed_rejected(GSERIALIZED *g)
{
@@ -703,6 +734,7 @@ void gserialized2_suite_setup(void)
PG_ADD_TEST(suite, test_gserialized2_peek_first_point);
PG_ADD_TEST(suite, test_gserialized2_malformed_collection_count);
PG_ADD_TEST(suite, test_gserialized2_malformed_nurbs_degree);
+ PG_ADD_TEST(suite, test_gserialized2_malformed_polygon_ring_count);
PG_ADD_TEST(suite, test_gserialized2_malformed_declared_size);
PG_ADD_TEST(suite, test_gserialized2_malformed_short_allocation);
PG_ADD_TEST(suite, test_gserialized2_wkb_roundtrip_float_rounded_box);
diff --git a/liblwgeom/gserialized1.c b/liblwgeom/gserialized1.c
index 4528a790f..2c68f2c71 100644
--- a/liblwgeom/gserialized1.c
+++ b/liblwgeom/gserialized1.c
@@ -273,6 +273,17 @@ gserialized1_pointarray_payload_size(uint32_t npoints, lwflags_t lwflags, size_t
return LW_SUCCESS;
}
+static int
+gserialized1_validate_polygon_ring_count(uint32_t npoints)
+{
+ if (npoints > 0 && npoints < 4)
+ {
+ lwerror("%s: invalid non-empty polygon ring point count %u", __func__, npoints);
+ return LW_FAILURE;
+ }
+ return LW_SUCCESS;
+}
+
static int
gserialized1_validate_geometry_buffer(uint8_t *data_ptr, uint8_t *data_end, lwflags_t lwflags, size_t *size)
{
@@ -1515,6 +1526,12 @@ static LWPOLY* lwpoly_from_gserialized1_buffer(uint8_t *data_ptr, lwflags_t lwfl
/* Read in the number of points. */
npoints = gserialized1_get_uint32_t(data_ptr);
data_ptr += 4;
+ if (gserialized1_validate_polygon_ring_count(npoints) == LW_FAILURE)
+ {
+ poly->nrings = i;
+ lwpoly_free(poly);
+ return NULL;
+ }
/* Make a point array for the ring, and move the ordinate pointer past the ring ordinates. */
poly->rings[i] = ptarray_construct_reference_data(FLAGS_GET_Z(lwflags), FLAGS_GET_M(lwflags), npoints, ordinate_ptr);
@@ -1718,7 +1735,10 @@ LWGEOM* lwgeom_from_gserialized1(const GSERIALIZED *g)
lwgeom = lwgeom_from_gserialized1_buffer(data_ptr, lwflags, &size);
if ( ! lwgeom )
+ {
lwerror("%s: unable create geometry", __func__); /* Ooops! */
+ return NULL;
+ }
lwgeom->type = lwtype;
lwgeom->flags = lwflags;
diff --git a/liblwgeom/gserialized2.c b/liblwgeom/gserialized2.c
index 7a89593c6..d8169979a 100644
--- a/liblwgeom/gserialized2.c
+++ b/liblwgeom/gserialized2.c
@@ -398,6 +398,17 @@ gserialized2_pointarray_payload_size(uint32_t npoints, lwflags_t lwflags, size_t
return LW_SUCCESS;
}
+static int
+gserialized2_validate_polygon_ring_count(uint32_t npoints)
+{
+ if (npoints > 0 && npoints < 4)
+ {
+ lwerror("%s: invalid non-empty polygon ring point count %u", __func__, npoints);
+ return LW_FAILURE;
+ }
+ return LW_SUCCESS;
+}
+
static int
gserialized2_validate_geometry_buffer(uint8_t *data_ptr, uint8_t *data_end, lwflags_t lwflags, size_t *size)
{
@@ -2054,6 +2065,12 @@ lwpoly_from_gserialized2_buffer(uint8_t *data_ptr, lwflags_t lwflags, size_t *si
/* Read in the number of points. */
npoints = gserialized2_get_uint32_t(data_ptr);
data_ptr += 4;
+ if (gserialized2_validate_polygon_ring_count(npoints) == LW_FAILURE)
+ {
+ poly->nrings = i;
+ lwpoly_free(poly);
+ return NULL;
+ }
/* Make a point array for the ring, and move the ordinate pointer past the ring ordinates. */
poly->rings[i] = ptarray_construct_reference_data(FLAGS_GET_Z(lwflags), FLAGS_GET_M(lwflags), npoints, ordinate_ptr);
@@ -2484,7 +2501,10 @@ LWGEOM* lwgeom_from_gserialized2(const GSERIALIZED *g)
lwgeom = lwgeom_from_gserialized2_buffer(data_ptr, lwflags, &size, srid);
if (!lwgeom)
+ {
lwerror("%s: unable create geometry", __func__); /* Ooops! */
+ return NULL;
+ }
lwgeom->type = lwtype;
lwgeom->flags = lwflags;
-----------------------------------------------------------------------
Summary of changes:
NEWS | 2 ++
liblwgeom/cunit/cu_gserialized1.c | 38 ++++++++++++++++++++++++++++++++++++++
liblwgeom/cunit/cu_gserialized2.c | 32 ++++++++++++++++++++++++++++++++
liblwgeom/gserialized1.c | 20 ++++++++++++++++++++
liblwgeom/gserialized2.c | 20 ++++++++++++++++++++
5 files changed, 112 insertions(+)
hooks/post-receive
--
PostGIS
More information about the postgis-tickets
mailing list