[SCM] PostGIS branch master updated. 3.7.0beta1-160-gc1e91f8b1e

git at osgeo.org git at osgeo.org
Sat Aug 1 11:10:43 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  c1e91f8b1e5612e8d7ec50b3a7705cab232103db (commit)
       via  8c966a0071b95040e96c741e2ee13e977c5d4b40 (commit)
      from  1df65a1b92af4bd5162d08c5fbc4fa3133ae46c8 (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 c1e91f8b1e5612e8d7ec50b3a7705cab232103db
Merge: 1df65a1b92 8c966a0071
Author: Darafei Praliaskouski <komzpa at gmail.com>
Date:   Sat Aug 1 11:10:36 2026 -0700

    Merge pull request 'liblwgeom: validate serialized NURBS invariants' (!640) from Komzpa/postgis:oss-fuzz-4885548695879680 into master
    
    Reject malformed GSERIALIZED v2 NURBS payloads before they can reach curve evaluation and bbox calculation. The validator now mirrors the constructor invariants for degree, control-point count, optional weight count, optional knot count, finite positive weights, and finite nondecreasing knots.
    
    This fixes the OSS-Fuzz gserialized_from_bytea_fuzzer testcase that reached lwnurbscurve_evaluate via lwgeom_calculate_gbox_cartesian.
    
    Affected releases: this is on the unreleased 3.7 development line. The maintained stable branches stable-3.2 through stable-3.6 do not contain the NURBS curve code path, and there is currently no stable-3.7 branch to backpatch.
    
    Validation:
    
    - clang ASAN focused build: ./configure CC=clang CXX=clang++ --without-raster --without-topology --without-sfcgal CFLAGS="-g3 -O0 -fno-omit-frame-pointer -fsanitize=address" CXXFLAGS="-g3 -O0 -fno-omit-frame-pointer -fsanitize=address" LDFLAGS="-fsanitize=address"
    - ./liblwgeom/cunit/cu_tester "serialization/deserialization v2" — 16 passed, 0 failed, 462 asserts
    - ./liblwgeom/cunit/cu_tester test_gserialized2_malformed_nurbs_degree — 9 asserts passed
    - tmp-fuzz-out/gserialized_from_bytea_fuzzer clusterfuzz-testcase-minimized-gserialized_from_bytea_fuzzer-4885548695879680 — exit 0
    
    Credit to OSS-Fuzz: https://oss-fuzz.com/testcase?key=4885548695879680
    
    Reviewed-on: https://gitea.osgeo.org/postgis/postgis/pulls/640


commit 8c966a0071b95040e96c741e2ee13e977c5d4b40
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Sat Aug 1 22:07:06 2026 +0400

    liblwgeom: validate serialized NURBS invariants
    
    Reject malformed GSERIALIZED v2 NURBS curves before deserialization can hand inconsistent degree, point, weight, or knot counts to curve evaluation and bbox calculation.
    
    Credit to OSS-Fuzz: https://oss-fuzz.com/testcase?key=4885548695879680

diff --git a/NEWS b/NEWS
index 34c494e701..1443dee89a 100644
--- a/NEWS
+++ b/NEWS
@@ -8,6 +8,8 @@ These are only changes since 3.7.0beta1.
 
 * Bug Fixes *
 
+ - [liblwgeom] Reject malformed GSERIALIZED NURBS before curve
+          evaluation (Darafei Praliaskouski)
  - Make the computed-columns regression test assert the query plan instead of racing two
           stopwatches, so it no longer fails at random under CI load
           (Darafei Praliaskouski)
diff --git a/liblwgeom/cunit/cu_gserialized2.c b/liblwgeom/cunit/cu_gserialized2.c
index 44be675094..c02c5b2f83 100644
--- a/liblwgeom/cunit/cu_gserialized2.c
+++ b/liblwgeom/cunit/cu_gserialized2.c
@@ -527,6 +527,8 @@ gserialized2_from_hexbytes(const char *hex)
 	return g;
 }
 
+static void assert_gserialized2_malformed_rejected(GSERIALIZED *g);
+
 static void
 test_gserialized2_malformed_collection_count(void)
 {
@@ -545,6 +547,28 @@ test_gserialized2_malformed_collection_count(void)
 	lwfree(g);
 }
 
+static void
+test_gserialized2_malformed_nurbs_degree(void)
+{
+	LWGEOM *lwgeom = lwgeom_from_wkt("NURBSCURVE(2, (0 0, 1 1, 2 0))", LW_PARSER_CHECK_NONE);
+	GSERIALIZED *g;
+	uint32_t *payload;
+
+	CU_ASSERT_PTR_NOT_NULL_FATAL(lwgeom);
+	g = gserialized2_from_lwgeom(lwgeom, NULL);
+	CU_ASSERT_PTR_NOT_NULL_FATAL(g);
+
+	payload = (uint32_t *)gserialized2_get_geometry_p(g);
+	CU_ASSERT_EQUAL(payload[0], NURBSCURVETYPE);
+	CU_ASSERT_EQUAL(payload[1], 3);
+	payload[2] = 3; /* Three control points cannot support a degree-3 curve. */
+
+	assert_gserialized2_malformed_rejected(g);
+
+	lwgeom_free(lwgeom);
+	lwfree(g);
+}
+
 static void
 assert_gserialized2_malformed_rejected(GSERIALIZED *g)
 {
@@ -658,6 +682,7 @@ void gserialized2_suite_setup(void)
 	PG_ADD_TEST(suite, test_gserialized2_extended_flags);
 	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_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/gserialized2.c b/liblwgeom/gserialized2.c
index 813098c77a..7a89593c6d 100644
--- a/liblwgeom/gserialized2.c
+++ b/liblwgeom/gserialized2.c
@@ -68,6 +68,77 @@ static int gserialized2_read_gbox_p(const GSERIALIZED *g, GBOX *gbox);
 static int gserialized2_payload_bounds(const GSERIALIZED *g, uint8_t **start, uint8_t **end);
 static int gserialized2_validate_geometry_buffer(uint8_t *data_ptr, uint8_t *data_end, lwflags_t lwflags, size_t *size);
 
+static int
+gserialized2_validate_nurbs(uint32_t npoints,
+			    uint32_t degree,
+			    uint32_t nweights,
+			    uint32_t nknots,
+			    const double *weights,
+			    const double *knots)
+{
+	uint32_t i;
+	size_t expected_nknots;
+
+	if (degree < 1 || degree > 10)
+	{
+		lwerror("NURBS: degree %u outside valid range [1,10]", degree);
+		return LW_FAILURE;
+	}
+
+	if (npoints == 0)
+	{
+		if (nweights != 0 || nknots != 0)
+		{
+			lwerror("NURBS: empty curve cannot declare weights or knots");
+			return LW_FAILURE;
+		}
+		return LW_SUCCESS;
+	}
+
+	if (npoints < degree + 1)
+	{
+		lwerror("NURBS: npoints (%u) must be at least degree + 1 (%u)", npoints, degree + 1);
+		return LW_FAILURE;
+	}
+
+	if (nweights > 0 && nweights != npoints)
+	{
+		lwerror("NURBS: nweights (%u) must equal number of control points (%u)", nweights, npoints);
+		return LW_FAILURE;
+	}
+
+	expected_nknots = (size_t)npoints + degree + 1;
+	if (nknots > 0 && nknots != expected_nknots)
+	{
+		lwerror("NURBS: nknots (%u) must equal npoints + degree + 1 (%zu)", nknots, expected_nknots);
+		return LW_FAILURE;
+	}
+
+	for (i = 0; i < nweights; i++)
+	{
+		if (!isfinite(weights[i]) || weights[i] <= 0.0)
+		{
+			lwerror("NURBS: weight[%u] = %g must be finite and > 0", i, weights[i]);
+			return LW_FAILURE;
+		}
+	}
+
+	for (i = 1; i < nknots; i++)
+	{
+		if (!isfinite(knots[i]) || knots[i] < knots[i - 1])
+		{
+			lwerror("NURBS: knot[%u] = %g must be finite and >= knot[%u] = %g",
+				i,
+				knots[i],
+				i - 1,
+				knots[i - 1]);
+			return LW_FAILURE;
+		}
+	}
+
+	return LW_SUCCESS;
+}
+
 static size_t
 gserialized2_buffer_size(const GSERIALIZED *g)
 {
@@ -441,14 +512,16 @@ gserialized2_validate_geometry_buffer(uint8_t *data_ptr, uint8_t *data_end, lwfl
 	}
 
 	case NURBSCURVETYPE: {
-		uint32_t nweights, nknots;
+		uint32_t degree, nweights, nknots;
 		size_t weight_bytes, knot_bytes, point_bytes;
+		const uint8_t *weights_ptr, *knots_ptr;
 		consumed = 6 * sizeof(uint32_t);
 		if (!gserialized2_range_available(data_ptr, data_end, consumed))
 		{
 			lwerror("%s: GSERIALIZED NURBS header exceeds payload size", __func__);
 			return LW_FAILURE;
 		}
+		degree = gserialized2_read_uint32_checked(data_ptr + 2 * sizeof(uint32_t), data_end, "NURBS degree");
 		nweights =
 		    gserialized2_read_uint32_checked(data_ptr + 3 * sizeof(uint32_t), data_end, "NURBS weight count");
 		nknots =
@@ -468,6 +541,12 @@ gserialized2_validate_geometry_buffer(uint8_t *data_ptr, uint8_t *data_end, lwfl
 			lwerror("%s: GSERIALIZED NURBS size overflows", __func__);
 			return LW_FAILURE;
 		}
+		weights_ptr = data_ptr + 6 * sizeof(uint32_t);
+		knots_ptr = weights_ptr + weight_bytes;
+		if (gserialized2_validate_nurbs(
+			count, degree, nweights, nknots, (const double *)weights_ptr, (const double *)knots_ptr) ==
+		    LW_FAILURE)
+			return LW_FAILURE;
 		break;
 	}
 
@@ -2219,6 +2298,17 @@ lwnurbscurve_from_gserialized2_buffer(uint8_t *data_ptr, lwflags_t lwflags, size
     /* Skip 4-byte pad to align following doubles (weights/knots/coords) */
     data_ptr += sizeof(uint32_t);
 
+    if (gserialized2_validate_nurbs(npoints,
+				    degree,
+				    nweights,
+				    nknots,
+				    (const double *)data_ptr,
+				    (const double *)(data_ptr + sizeof(double) * nweights)) == LW_FAILURE)
+    {
+	    lwfree(curve);
+	    return NULL;
+    }
+
     /*
      * VARIABLE SECTION 1: Read weight values (if any)
      *

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

Summary of changes:
 NEWS                              |  2 +
 liblwgeom/cunit/cu_gserialized2.c | 25 +++++++++++
 liblwgeom/gserialized2.c          | 92 ++++++++++++++++++++++++++++++++++++++-
 3 files changed, 118 insertions(+), 1 deletion(-)


hooks/post-receive
-- 
PostGIS


More information about the postgis-tickets mailing list