[SCM] PostGIS branch stable-3.5 updated. 3.5.7-86-g35a806b05b

git at osgeo.org git at osgeo.org
Mon Jul 27 12:09:37 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, stable-3.5 has been updated
       via  35a806b05be9bc41df224755436b03d8b9072763 (commit)
       via  2bf528cbf25deb22a64374013e5a1d89100e01b5 (commit)
       via  efe6cabdd553923e57f21a429d871f46e0ae556f (commit)
       via  a98cd9c90a78aa0c82f1cebdc13449c303ef4890 (commit)
       via  133a0b59760fff65920946c403c0f5c8f04fc513 (commit)
      from  1132702d6b108279d0d66ed93dbdfef704e0e729 (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 35a806b05be9bc41df224755436b03d8b9072763
Merge: 1132702d6b 2bf528cbf2
Author: Darafei Praliaskouski <komzpa at gmail.com>
Date:   Mon Jul 27 12:09:35 2026 -0700

    Merge pull request 'Backport safety fixes to stable-3.5' (!538) from Komzpa/postgis:backport/20260726-stable-3.5-safety-fixes into stable-3.5
    
    Reviewed-on: https://gitea.osgeo.org/postgis/postgis/pulls/538


commit 2bf528cbf25deb22a64374013e5a1d89100e01b5
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Sun Jul 26 21:35:41 2026 +0400

    Drop master-only NURBS validator from stable GSERIALIZED port
    
    Stable branches do not define NURBSCURVETYPE, so the NURBS-specific validator from the master GSERIALIZED hardening patch is not a branch-capability match. Keep the payload checks for geometry types present on the stable line.
    
    (cherry picked from commit de00ccc19b0fee48f8dbbc8b521510382624c099)
    (cherry picked from commit 393414009f1aad24c297438d88de569453b8cc1c)

diff --git a/liblwgeom/gserialized2.c b/liblwgeom/gserialized2.c
index 3281e0b1e9..215bf389c3 100644
--- a/liblwgeom/gserialized2.c
+++ b/liblwgeom/gserialized2.c
@@ -440,37 +440,6 @@ gserialized2_validate_geometry_buffer(uint8_t *data_ptr, uint8_t *data_end, lwfl
 		break;
 	}
 
-	case NURBSCURVETYPE: {
-		uint32_t nweights, nknots;
-		size_t weight_bytes, knot_bytes, point_bytes;
-		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;
-		}
-		nweights =
-		    gserialized2_read_uint32_checked(data_ptr + 3 * sizeof(uint32_t), data_end, "NURBS weight count");
-		nknots =
-		    gserialized2_read_uint32_checked(data_ptr + 4 * sizeof(uint32_t), data_end, "NURBS knot count");
-		if (gserialized2_checked_mul((size_t)nweights, sizeof(double), &weight_bytes) == LW_FAILURE ||
-		    gserialized2_checked_mul((size_t)nknots, sizeof(double), &knot_bytes) == LW_FAILURE)
-		{
-			lwerror("%s: GSERIALIZED NURBS vector size overflows", __func__);
-			return LW_FAILURE;
-		}
-		if (gserialized2_pointarray_payload_size(count, lwflags, &point_bytes) == LW_FAILURE)
-			return LW_FAILURE;
-		if (gserialized2_checked_add(consumed, weight_bytes, &consumed) == LW_FAILURE ||
-		    gserialized2_checked_add(consumed, knot_bytes, &consumed) == LW_FAILURE ||
-		    gserialized2_checked_add(consumed, point_bytes, &consumed) == LW_FAILURE)
-		{
-			lwerror("%s: GSERIALIZED NURBS size overflows", __func__);
-			return LW_FAILURE;
-		}
-		break;
-	}
-
 	default:
 		lwerror("Unknown geometry type: %d - %s", type, lwtype_name(type));
 		return LW_FAILURE;

commit efe6cabdd553923e57f21a429d871f46e0ae556f
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Sun Jul 26 04:15:53 2026 +0400

    Guard largest empty circle boundary inputs
    
    (cherry picked from commit 8d143e4b549567d362edd0e80349bd5de29a4c1e)
    (cherry picked from commit 42a45ea889ec7ca371bcd1b9f9272ab10bcd21d3)

diff --git a/NEWS b/NEWS
index e02bbea9e9..8dad5ef3b5 100644
--- a/NEWS
+++ b/NEWS
@@ -7,6 +7,8 @@ PostGIS 3.5.8
     geometry data (Darafei Praliaskouski)
   - GT-523, [raster] Fix invalid reads in geotransform property access
     (Darafei Praliaskouski)
+  - GT-519, Reject invalid ST_LargestEmptyCircle boundaries before GEOS can
+    spend unbounded time on them (Darafei Praliaskouski)
   - #6046, Prefer GraphicsMagick for documentation image generation
            (Darafei Praliaskouski)
   - #6094, [upgrade] Avoid legacy string literal warnings in downgrade checks
diff --git a/doc/reference_processing.xml b/doc/reference_processing.xml
index e4d8f129c2..cc46abf850 100644
--- a/doc/reference_processing.xml
+++ b/doc/reference_processing.xml
@@ -1648,6 +1648,7 @@ LINESTRING Z (-30 -29.7 5,-29 -27 11,-30 -29.7 10,-36 -31 5,-45 -33 1,-46 -32 11
       <refsection>
         <title>Description</title>
         <para>Finds the largest circle which does not overlap a set of point and line obstacles. (Polygonal geometries may be included as obstacles, but only their boundary lines are used.) The center of the circle is constrained to lie inside a polygonal boundary, which by default is the convex hull of the input geometry. The circle center is the point in the interior of the boundary which has the farthest distance from the obstacles. The circle itself is provided by the center point and a nearest point lying on an obstacle determining the circle radius.</para>
+        <para>If a boundary is supplied it must be a valid polygonal geometry with positive area.</para>
         <para>The circle center is determined to a given  accuracy specified by a distance tolerance, using an iterative algorithm. If the accuracy distance is not specified a reasonable default is used.</para>
         <para>Returns a record with fields:</para>
 
diff --git a/postgis/lwgeom_geos.c b/postgis/lwgeom_geos.c
index a1d07df367..f8cbe80ed8 100644
--- a/postgis/lwgeom_geos.c
+++ b/postgis/lwgeom_geos.c
@@ -438,8 +438,18 @@ Datum ST_LargestEmptyCircle(PG_FUNCTION_ARGS)
 		GEOSGeometry *ginput, *gcircle, *gcenter, *gnearest;
 		GEOSGeometry *gboundary = NULL;
 		double width, height, size;
-		GBOX gbox;
-		LWGEOM *lwg;
+		double boundary_area;
+		GBOX gbox, boundary_gbox;
+		LWGEOM *lwg, *lwboundary;
+
+		if (hasBoundary &&
+		    (!gserialized_get_gbox_p(boundary, &boundary_gbox) ||
+		     !(boundary_gbox.xmax > boundary_gbox.xmin && boundary_gbox.ymax > boundary_gbox.ymin)))
+		{
+			lwpgerror("Boundary geometry must have positive area");
+			PG_RETURN_NULL();
+		}
+
 		lwg = lwgeom_from_gserialized(geom);
 		if (!lwgeom_isfinite(lwg))
 		{
@@ -460,6 +470,24 @@ Datum ST_LargestEmptyCircle(PG_FUNCTION_ARGS)
 			tolerance = size / 1000.0;
 		}
 
+		if (hasBoundary)
+		{
+			lwboundary = lwgeom_from_gserialized(boundary);
+			if (!lwgeom_isfinite(lwboundary))
+			{
+				lwgeom_free(lwboundary);
+				lwpgerror("Boundary geometry contains invalid coordinates");
+				PG_RETURN_NULL();
+			}
+			boundary_area = lwgeom_area(lwboundary);
+			lwgeom_free(lwboundary);
+			if (!(boundary_area > 0.0))
+			{
+				lwpgerror("Boundary geometry must have positive area");
+				PG_RETURN_NULL();
+			}
+		}
+
 		initGEOS(lwpgnotice, lwgeom_geos_error);
 
 		ginput = POSTGIS2GEOS(geom);
@@ -468,9 +496,24 @@ Datum ST_LargestEmptyCircle(PG_FUNCTION_ARGS)
 
 		if (hasBoundary)
 		{
+			char boundary_valid;
 			gboundary = POSTGIS2GEOS(boundary);
 			if (!gboundary)
 				HANDLE_GEOS_ERROR("Boundary could not be converted to GEOS");
+			boundary_valid = GEOSisValid(gboundary);
+			if (boundary_valid == 2)
+			{
+				GEOSGeom_destroy(ginput);
+				GEOSGeom_destroy(gboundary);
+				HANDLE_GEOS_ERROR("GEOSisValid");
+			}
+			if (!boundary_valid)
+			{
+				GEOSGeom_destroy(ginput);
+				GEOSGeom_destroy(gboundary);
+				lwpgerror("Boundary geometry is invalid");
+				PG_RETURN_NULL();
+			}
 		}
 
 		gcircle = GEOSLargestEmptyCircle(ginput, gboundary, tolerance);
diff --git a/regress/core/geos39.sql b/regress/core/geos39.sql
index 9d5e025538..ab55d198ec 100644
--- a/regress/core/geos39.sql
+++ b/regress/core/geos39.sql
@@ -52,3 +52,9 @@ SELECT 'lec-1', round(radius::numeric,3),
   ST_AsText(nearest,3) AS nearest
 FROM ST_LargestEmptyCircle(
   'MULTIPOINT ((4 3), (7 6), (4 6))');
+
+SELECT 'lec-invalid-boundary', radius
+FROM ST_LargestEmptyCircle(
+  'POINT(0 0)'::geometry,
+  20.1,
+  'POLYGON((1 2,1 2,1 2,1 2,3 2,1 2))'::geometry);
diff --git a/regress/core/geos39_expected b/regress/core/geos39_expected
index cc507a7f4e..ba385d2029 100644
--- a/regress/core/geos39_expected
+++ b/regress/core/geos39_expected
@@ -10,3 +10,4 @@ rp-2|POINT(1 19)
 rp-3|POINT(0 20)
 ERROR:  Geometry contains invalid coordinates
 lec-1|2.121|POINT(5.5 4.5)|POINT(4 3)
+ERROR:  Boundary geometry must have positive area

commit a98cd9c90a78aa0c82f1cebdc13449c303ef4890
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Sun Jul 26 16:14:42 2026 +0300

    Fix raster geotransform invalid reads
    
    Port the stable-applicable part of the Valgrind finding: RASTER_getGeotransform read raster offsets after destroying the deserialized raster. The rt_band_init_value zero-cell fix from master does not apply because stable-3.5 does not have that helper.
    
    Ported-from: https://gitea.osgeo.org/postgis/postgis/commit/5692dfe21b854899ee738f278651e58e63532652
    (cherry picked from commit 0181cd4411467c12ac96a935bacb7da98ddac027)

diff --git a/NEWS b/NEWS
index 84055137c0..e02bbea9e9 100644
--- a/NEWS
+++ b/NEWS
@@ -5,6 +5,8 @@ PostGIS 3.5.8
 
   - GT-522, Reject malformed GSERIALIZED payload counts before deserializing
     geometry data (Darafei Praliaskouski)
+  - GT-523, [raster] Fix invalid reads in geotransform property access
+    (Darafei Praliaskouski)
   - #6046, Prefer GraphicsMagick for documentation image generation
            (Darafei Praliaskouski)
   - #6094, [upgrade] Avoid legacy string literal warnings in downgrade checks
diff --git a/raster/rt_pg/rtpg_raster_properties.c b/raster/rt_pg/rtpg_raster_properties.c
index 032e4975e9..c2717585e0 100644
--- a/raster/rt_pg/rtpg_raster_properties.c
+++ b/raster/rt_pg/rtpg_raster_properties.c
@@ -445,10 +445,8 @@ Datum RASTER_getGeotransform(PG_FUNCTION_ARGS)
     double jmag;
     double theta_i;
     double theta_ij;
-		/*
     double xoffset;
     double yoffset;
-		*/
 
     TupleDesc result_tuple; /* for returning a composite */
     Datum values[VALUES_LENGTH];
@@ -478,6 +476,8 @@ Datum RASTER_getGeotransform(PG_FUNCTION_ARGS)
             rt_raster_get_y_skew(raster),
             rt_raster_get_y_scale(raster),
             &imag, &jmag, &theta_i, &theta_ij) ;
+    xoffset = rt_raster_get_x_offset(raster);
+    yoffset = rt_raster_get_y_offset(raster);
 
     rt_raster_destroy(raster);
     PG_FREE_IF_COPY(pgraster, 0);
@@ -501,8 +501,8 @@ Datum RASTER_getGeotransform(PG_FUNCTION_ARGS)
     values[1] = Float8GetDatum(jmag);
     values[2] = Float8GetDatum(theta_i);
     values[3] = Float8GetDatum(theta_ij);
-    values[4] = Float8GetDatum(rt_raster_get_x_offset(raster));
-    values[5] = Float8GetDatum(rt_raster_get_y_offset(raster));
+    values[4] = Float8GetDatum(xoffset);
+    values[5] = Float8GetDatum(yoffset);
 
     memset(nulls, FALSE, sizeof(bool) * VALUES_LENGTH);
 

commit 133a0b59760fff65920946c403c0f5c8f04fc513
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Sun Jul 26 14:43:56 2026 +0400

    Harden GSERIALIZED payload readers
    
    (cherry picked from commit 62cff7ebdd8a76e61231b379026a41dd6e49b436)

diff --git a/NEWS b/NEWS
index 2c5e2ad055..84055137c0 100644
--- a/NEWS
+++ b/NEWS
@@ -3,6 +3,8 @@ PostGIS 3.5.8
 
 * Bug Fixes *
 
+  - GT-522, Reject malformed GSERIALIZED payload counts before deserializing
+    geometry data (Darafei Praliaskouski)
   - #6046, Prefer GraphicsMagick for documentation image generation
            (Darafei Praliaskouski)
   - #6094, [upgrade] Avoid legacy string literal warnings in downgrade checks
diff --git a/liblwgeom/cunit/cu_gserialized1.c b/liblwgeom/cunit/cu_gserialized1.c
index 47fb42e02e..fe5c2ca413 100644
--- a/liblwgeom/cunit/cu_gserialized1.c
+++ b/liblwgeom/cunit/cu_gserialized1.c
@@ -1252,6 +1252,47 @@ test_gserialized1_peek_first_point(void)
 	CU_ASSERT(peek1_point_helper("POLYGON((0 0, 1 1, 1 0, 0 0))", &p) == LW_FAILURE);
 }
 
+static void
+assert_gserialized1_malformed_rejected(GSERIALIZED *g)
+{
+	LWGEOM *geom;
+	GBOX box;
+	POINT4D point;
+
+	memset(&box, 0, sizeof(box));
+	memset(&point, 0, sizeof(point));
+
+	cu_error_msg_reset();
+	geom = lwgeom_from_gserialized1(g);
+	CU_ASSERT_PTR_NULL(geom);
+	CU_ASSERT_NOT_EQUAL(strlen(cu_error_msg), 0);
+
+	CU_ASSERT_TRUE(gserialized1_is_empty(g));
+	CU_ASSERT_EQUAL(gserialized1_peek_gbox_p(g, &box), LW_FAILURE);
+	CU_ASSERT_EQUAL(gserialized1_peek_first_point(g, &point), LW_FAILURE);
+}
+
+static void
+test_gserialized1_malformed_declared_size(void)
+{
+	LWGEOM *lwgeom = lwgeom_from_wkt("POINT(1 1)", LW_PARSER_CHECK_NONE);
+	GSERIALIZED *g;
+	size_t size = 0;
+
+	CU_ASSERT_PTR_NOT_NULL_FATAL(lwgeom);
+	g = gserialized1_from_lwgeom(lwgeom, &size);
+	CU_ASSERT_PTR_NOT_NULL_FATAL(g);
+
+	LWSIZE_SET(g->size, 0);
+	assert_gserialized1_malformed_rejected(g);
+
+	LWSIZE_SET(g->size, offsetof(GSERIALIZED, data) - 1);
+	assert_gserialized1_malformed_rejected(g);
+
+	lwfree(g);
+	lwgeom_free(lwgeom);
+}
+
 /*
 ** Used by test harness to register the tests in this file.
 */
@@ -1286,4 +1327,5 @@ 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_declared_size);
 }
diff --git a/liblwgeom/cunit/cu_gserialized2.c b/liblwgeom/cunit/cu_gserialized2.c
index dd32828c75..6ffdc5badc 100644
--- a/liblwgeom/cunit/cu_gserialized2.c
+++ b/liblwgeom/cunit/cu_gserialized2.c
@@ -509,6 +509,133 @@ test_gserialized2_peek_first_point(void)
 	CU_ASSERT(peek2_point_helper("POLYGON((0 0, 1 1, 1 0, 0 0))", &p) == LW_FAILURE);
 }
 
+static GSERIALIZED *
+gserialized2_from_hexbytes(const char *hex)
+{
+	uint8_t *bytes;
+	size_t size;
+	GSERIALIZED *g;
+
+	size = strlen(hex) / 2;
+	bytes = bytes_from_hexbytes(hex, strlen(hex));
+	g = lwalloc(size);
+	memcpy(g, bytes, size);
+	lwfree(bytes);
+	return g;
+}
+
+static void
+test_gserialized2_malformed_collection_count(void)
+{
+	const char *hex =
+	    "8001000000000044000000000000803f0000000000000040"
+	    "070000000200000001000000f03f000000000000f03f";
+	GSERIALIZED *g = gserialized2_from_hexbytes(hex);
+	LWGEOM *geom;
+
+	cu_error_msg_reset();
+	geom = lwgeom_from_gserialized2(g);
+
+	CU_ASSERT_PTR_NULL(geom);
+	CU_ASSERT_NOT_EQUAL(strlen(cu_error_msg), 0);
+
+	lwfree(g);
+}
+
+static void
+assert_gserialized2_malformed_rejected(GSERIALIZED *g)
+{
+	LWGEOM *geom;
+	GBOX box;
+	POINT4D point;
+
+	memset(&box, 0, sizeof(box));
+	memset(&point, 0, sizeof(point));
+
+	cu_error_msg_reset();
+	geom = lwgeom_from_gserialized2(g);
+	CU_ASSERT_PTR_NULL(geom);
+	CU_ASSERT_NOT_EQUAL(strlen(cu_error_msg), 0);
+
+	CU_ASSERT_TRUE(gserialized2_is_empty(g));
+	CU_ASSERT_EQUAL(gserialized2_peek_gbox_p(g, &box), LW_FAILURE);
+	CU_ASSERT_EQUAL(gserialized2_peek_first_point(g, &point), LW_FAILURE);
+}
+
+static void
+test_gserialized2_malformed_declared_size(void)
+{
+	const char *declared_size_0 =
+	    "0000000000000044000000000000803f0000000000000040"
+	    "070000000200000001000000f03f000000000000f03f";
+	const char *declared_size_23 =
+	    "5c00000000000044000000000000803f0000000000000040"
+	    "070000000200000001000000f03f000000000000f03f";
+	GSERIALIZED *g;
+
+	g = gserialized2_from_hexbytes(declared_size_0);
+	assert_gserialized2_malformed_rejected(g);
+	lwfree(g);
+
+	g = gserialized2_from_hexbytes(declared_size_23);
+	assert_gserialized2_malformed_rejected(g);
+	lwfree(g);
+}
+
+static void
+test_gserialized2_malformed_short_allocation(void)
+{
+#if defined(POSTGIS_ASAN_ALLOCATOR_SIZE)
+	const char *hex = "8001000000000044";
+	uint8_t *bytes = bytes_from_hexbytes(hex, strlen(hex));
+	GSERIALIZED *g = malloc(strlen(hex) / 2);
+
+	CU_ASSERT_PTR_NOT_NULL_FATAL(g);
+	memcpy(g, bytes, strlen(hex) / 2);
+	assert_gserialized2_malformed_rejected(g);
+
+	free(g);
+	lwfree(bytes);
+#else
+	CU_PASS("shorter-than-declared allocation validation requires AddressSanitizer allocation metadata");
+#endif
+}
+
+static void
+test_gserialized2_wkb_roundtrip_float_rounded_box(void)
+{
+	const char *hex =
+	    "0102000000030000000000000000000000000000f03f0000000000000040"
+	    "0101000000000000000f03f0000000000000040000000000000f03f";
+	size_t wkb_size = strlen(hex) / 2;
+	uint8_t *wkb = bytes_from_hexbytes(hex, strlen(hex));
+	LWGEOM *input = lwgeom_from_wkb(wkb, wkb_size, LW_PARSER_CHECK_NONE);
+	GSERIALIZED *serialized;
+	LWGEOM *roundtrip;
+	size_t serialized_size = 0;
+	GBOX input_box;
+	GBOX roundtrip_box;
+
+	CU_ASSERT_PTR_NOT_NULL_FATAL(input);
+	serialized = gserialized2_from_lwgeom(input, &serialized_size);
+	CU_ASSERT_PTR_NOT_NULL_FATAL(serialized);
+	roundtrip = lwgeom_from_gserialized2(serialized);
+	CU_ASSERT_PTR_NOT_NULL_FATAL(roundtrip);
+
+	CU_ASSERT_EQUAL(serialized_size, LWSIZE_GET(serialized->size));
+	CU_ASSERT_PTR_NOT_NULL(input->bbox);
+	CU_ASSERT_PTR_NOT_NULL(roundtrip->bbox);
+	CU_ASSERT_FALSE(gbox_same(input->bbox, roundtrip->bbox));
+	CU_ASSERT_EQUAL(lwgeom_calculate_gbox_cartesian(input, &input_box), LW_SUCCESS);
+	CU_ASSERT_EQUAL(lwgeom_calculate_gbox_cartesian(roundtrip, &roundtrip_box), LW_SUCCESS);
+	CU_ASSERT_TRUE(gbox_same(&input_box, &roundtrip_box));
+
+	lwgeom_free(input);
+	lwgeom_free(roundtrip);
+	lwfree(serialized);
+	lwfree(wkb);
+}
+
 /*
 ** Used by test harness to register the tests in this file.
 */
@@ -527,4 +654,8 @@ void gserialized2_suite_setup(void)
 	PG_ADD_TEST(suite, test_gserialized2_peek_gbox_p_fails_for_unsupported_cases);
 	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_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 02b9e2bdfb..1296f3dc2c 100644
--- a/liblwgeom/gserialized1.c
+++ b/liblwgeom/gserialized1.c
@@ -30,13 +30,37 @@
 #include "gserialized1.h"
 
 #include <stddef.h>
+#if defined(__has_feature)
+#if __has_feature(address_sanitizer)
+#define POSTGIS_ASAN_ALLOCATOR_SIZE 1
+#endif
+#endif
+#if defined(__SANITIZE_ADDRESS__)
+#define POSTGIS_ASAN_ALLOCATOR_SIZE 1
+#endif
+#if defined(POSTGIS_ASAN_ALLOCATOR_SIZE)
+#include <sanitizer/allocator_interface.h>
+#endif
 
 /***********************************************************************
 * GSERIALIZED metadata utility functions.
 */
 
 static int gserialized1_read_gbox_p(const GSERIALIZED *g, GBOX *gbox);
+static int gserialized1_payload_bounds(const GSERIALIZED *g, uint8_t **start, uint8_t **end);
+static int gserialized1_validate_geometry_buffer(uint8_t *data_ptr, uint8_t *data_end, lwflags_t lwflags, size_t *size);
 
+static size_t
+gserialized1_buffer_size(const GSERIALIZED *g)
+{
+	size_t gsize = LWSIZE_GET(g->size);
+#if defined(POSTGIS_ASAN_ALLOCATOR_SIZE)
+	size_t allocated_size = __sanitizer_get_allocated_size(g);
+	if (allocated_size > 0 && allocated_size < gsize)
+		return allocated_size;
+#endif
+	return gsize;
+}
 
 lwflags_t gserialized1_get_lwflags(const GSERIALIZED *g)
 {
@@ -132,6 +156,13 @@ static uint32_t gserialized1_header_size(const GSERIALIZED *gser)
 uint32_t gserialized1_get_type(const GSERIALIZED *g)
 {
 	uint32_t *ptr;
+	size_t hsz = gserialized1_header_size(g);
+	size_t gsize = gserialized1_buffer_size(g);
+	if (gsize < hsz + sizeof(uint32_t))
+	{
+		lwerror("%s: GSERIALIZED too small for geometry type", __func__);
+		return 0;
+	}
 	ptr = (uint32_t*)(g->data);
 	if ( G1FLAGS_GET_BBOX(g->gflags) )
 	{
@@ -142,19 +173,19 @@ uint32_t gserialized1_get_type(const GSERIALIZED *g)
 
 int32_t gserialized1_get_srid(const GSERIALIZED *s)
 {
-	int32_t srid = 0;
-	srid = srid | (s->srid[0] << 16);
-	srid = srid | (s->srid[1] << 8);
-	srid = srid | s->srid[2];
-	/* Only the first 21 bits are set. Slide up and back to pull
-	   the negative bits down, if we need them. */
-	srid = (srid<<11)>>11;
+	uint32_t srid = 0;
+	srid = srid | ((uint32_t)s->srid[0] << 16);
+	srid = srid | ((uint32_t)s->srid[1] << 8);
+	srid = srid | (uint32_t)s->srid[2];
+	/* Only the first 21 bits are set. Sign-extend without signed shift UB. */
+	if (srid & 0x00100000)
+		srid |= 0xFFE00000;
 
 	/* 0 is our internal unknown value. We'll map back and forth here for now */
 	if ( srid == 0 )
 		return SRID_UNKNOWN;
 	else
-		return srid;
+		return (int32_t)srid;
 }
 
 void gserialized1_set_srid(GSERIALIZED *s, int32_t srid)
@@ -173,6 +204,204 @@ void gserialized1_set_srid(GSERIALIZED *s, int32_t srid)
 	s->srid[2] = (srid & 0x000000FF);
 }
 
+static int
+gserialized1_range_available(const uint8_t *ptr, const uint8_t *end, size_t len)
+{
+	return ptr <= end && len <= (size_t)(end - ptr);
+}
+
+static int
+gserialized1_checked_mul(size_t a, size_t b, size_t *out)
+{
+	if (a != 0 && b > SIZE_MAX / a)
+		return LW_FAILURE;
+	*out = a * b;
+	return LW_SUCCESS;
+}
+
+static int
+gserialized1_checked_add(size_t a, size_t b, size_t *out)
+{
+	if (b > SIZE_MAX - a)
+		return LW_FAILURE;
+	*out = a + b;
+	return LW_SUCCESS;
+}
+
+static int
+gserialized1_payload_bounds(const GSERIALIZED *g, uint8_t **start, uint8_t **end)
+{
+	size_t hsz;
+	size_t gsize;
+
+	if (!g)
+		return LW_FAILURE;
+
+	hsz = gserialized1_header_size(g);
+	gsize = gserialized1_buffer_size(g);
+	if (gsize < hsz)
+		return LW_FAILURE;
+
+	if (start)
+		*start = (uint8_t *)g + hsz;
+	if (end)
+		*end = (uint8_t *)g + gsize;
+	return LW_SUCCESS;
+}
+
+static uint32_t
+gserialized1_read_uint32_checked(uint8_t *ptr, uint8_t *end, const char *field)
+{
+	uint32_t value = 0;
+	if (!gserialized1_range_available(ptr, end, sizeof(uint32_t)))
+	{
+		lwerror("%s: GSERIALIZED too small for %s", __func__, field);
+		return 0;
+	}
+	memcpy(&value, ptr, sizeof(uint32_t));
+	return value;
+}
+
+static int
+gserialized1_pointarray_payload_size(uint32_t npoints, lwflags_t lwflags, size_t *nbytes)
+{
+	if (gserialized1_checked_mul((size_t)npoints, sizeof(double) * FLAGS_NDIMS(lwflags), nbytes) == LW_FAILURE)
+	{
+		lwerror("%s: GSERIALIZED point count overflows payload size", __func__);
+		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)
+{
+	uint32_t type, count;
+	size_t consumed = 0;
+
+	if (!gserialized1_range_available(data_ptr, data_end, 2 * sizeof(uint32_t)))
+	{
+		lwerror("%s: GSERIALIZED geometry payload is too short", __func__);
+		return LW_FAILURE;
+	}
+
+	type = gserialized1_read_uint32_checked(data_ptr, data_end, "geometry type");
+	count = gserialized1_read_uint32_checked(data_ptr + sizeof(uint32_t), data_end, "geometry count");
+
+	switch (type)
+	{
+	case POINTTYPE:
+		if (count > 1)
+		{
+			lwerror("%s: invalid point count %u", __func__, count);
+			return LW_FAILURE;
+		}
+		/* fall through */
+	case LINETYPE:
+	case CIRCSTRINGTYPE:
+	case TRIANGLETYPE: {
+		size_t point_bytes;
+		if (gserialized1_pointarray_payload_size(count, lwflags, &point_bytes) == LW_FAILURE)
+			return LW_FAILURE;
+		consumed = 2 * sizeof(uint32_t) + point_bytes;
+		break;
+	}
+
+	case POLYGONTYPE: {
+		size_t ring_counts_size, ring_counts_padded_size, point_bytes_sum = 0;
+		uint8_t *ring_counts = data_ptr + 2 * sizeof(uint32_t);
+		uint32_t i;
+
+		if (gserialized1_checked_mul((size_t)count, sizeof(uint32_t), &ring_counts_size) == LW_FAILURE)
+		{
+			lwerror("%s: GSERIALIZED ring count overflows payload size", __func__);
+			return LW_FAILURE;
+		}
+		ring_counts_padded_size = ring_counts_size + ((count % 2) ? sizeof(uint32_t) : 0);
+		if (!gserialized1_range_available(ring_counts, data_end, ring_counts_padded_size))
+		{
+			lwerror("%s: GSERIALIZED polygon ring table exceeds payload size", __func__);
+			return LW_FAILURE;
+		}
+
+		for (i = 0; i < count; i++)
+		{
+			size_t ring_point_bytes;
+			uint32_t npoints = gserialized1_read_uint32_checked(
+			    ring_counts + i * sizeof(uint32_t), data_end, "ring point count");
+			if (gserialized1_pointarray_payload_size(npoints, lwflags, &ring_point_bytes) == LW_FAILURE)
+				return LW_FAILURE;
+			if (gserialized1_checked_add(point_bytes_sum, ring_point_bytes, &point_bytes_sum) == LW_FAILURE)
+			{
+				lwerror("%s: GSERIALIZED polygon coordinate size overflows", __func__);
+				return LW_FAILURE;
+			}
+		}
+		consumed = 2 * sizeof(uint32_t) + ring_counts_padded_size;
+		if (gserialized1_checked_add(consumed, point_bytes_sum, &consumed) == LW_FAILURE)
+		{
+			lwerror("%s: GSERIALIZED polygon size overflows", __func__);
+			return LW_FAILURE;
+		}
+		break;
+	}
+
+	case MULTIPOINTTYPE:
+	case MULTILINETYPE:
+	case MULTIPOLYGONTYPE:
+	case COMPOUNDTYPE:
+	case CURVEPOLYTYPE:
+	case MULTICURVETYPE:
+	case MULTISURFACETYPE:
+	case POLYHEDRALSURFACETYPE:
+	case TINTYPE:
+	case COLLECTIONTYPE: {
+		uint32_t i;
+		uint8_t *subgeom_ptr = data_ptr + 2 * sizeof(uint32_t);
+		lwflags_t subflags = lwflags;
+		FLAGS_SET_BBOX(subflags, 0);
+		for (i = 0; i < count; i++)
+		{
+			size_t subsize = 0;
+			uint32_t subtype =
+			    gserialized1_read_uint32_checked(subgeom_ptr, data_end, "collection subtype");
+			if (!lwcollection_allows_subtype(type, subtype))
+			{
+				lwerror("Invalid subtype (%s) for collection type (%s)",
+					lwtype_name(subtype),
+					lwtype_name(type));
+				return LW_FAILURE;
+			}
+			if (gserialized1_validate_geometry_buffer(subgeom_ptr, data_end, subflags, &subsize) ==
+			    LW_FAILURE)
+				return LW_FAILURE;
+			if (subsize == 0)
+			{
+				lwerror("%s: GSERIALIZED collection member has zero size", __func__);
+				return LW_FAILURE;
+			}
+			subgeom_ptr += subsize;
+		}
+		consumed = (size_t)(subgeom_ptr - data_ptr);
+		break;
+	}
+
+	default:
+		lwerror("Unknown geometry type: %d - %s", type, lwtype_name(type));
+		return LW_FAILURE;
+	}
+
+	if (!gserialized1_range_available(data_ptr, data_end, consumed))
+	{
+		lwerror("%s: GSERIALIZED geometry payload exceeds declared size", __func__);
+		return LW_FAILURE;
+	}
+
+	if (size)
+		*size = consumed;
+	return LW_SUCCESS;
+}
+
 static size_t gserialized1_is_empty_recurse(const uint8_t *p, int *isempty);
 static size_t gserialized1_is_empty_recurse(const uint8_t *p, int *isempty)
 {
@@ -203,13 +432,17 @@ static size_t gserialized1_is_empty_recurse(const uint8_t *p, int *isempty)
 
 int gserialized1_is_empty(const GSERIALIZED *g)
 {
-	uint8_t *p = (uint8_t*)g;
+	uint8_t *p, *end;
 	int isempty = 0;
 	assert(g);
 
-	p += 8; /* Skip varhdr and srid/flags */
-	if(gserialized1_has_bbox(g))
-		p += gserialized1_box_size(g); /* Skip the box */
+	if (gserialized1_payload_bounds(g, &p, &end) == LW_FAILURE)
+	{
+		lwerror("%s: invalid GSERIALIZED header size", __func__);
+		return LW_TRUE;
+	}
+	if (gserialized1_validate_geometry_buffer(p, end, gserialized1_get_lwflags(g), NULL) == LW_FAILURE)
+		return LW_TRUE;
 
 	gserialized1_is_empty_recurse(p, &isempty);
 	return isempty;
@@ -254,6 +487,8 @@ int gserialized1_read_gbox_p(const GSERIALIZED *g, GBOX *gbox)
 
 	/* Null input! */
 	if ( ! ( g && gbox ) ) return LW_FAILURE;
+	if (gserialized1_buffer_size(g) < offsetof(GSERIALIZED, data) + gserialized1_box_size(g))
+		return LW_FAILURE;
 
 	/* Initialize the flags on the box */
 	gbox->flags = gserialized1_get_lwflags(g);
@@ -298,7 +533,16 @@ int gserialized1_read_gbox_p(const GSERIALIZED *g, GBOX *gbox)
 int
 gserialized1_peek_gbox_p(const GSERIALIZED *g, GBOX *gbox)
 {
-	uint32_t type = gserialized1_get_type(g);
+	uint32_t type;
+	uint8_t *geometry_start = NULL;
+	uint8_t *geometry_end = NULL;
+
+	if (gserialized1_payload_bounds(g, &geometry_start, &geometry_end) == LW_FAILURE)
+		return LW_FAILURE;
+	if (gserialized1_validate_geometry_buffer(geometry_start, geometry_end, gserialized1_get_lwflags(g), NULL) ==
+	    LW_FAILURE)
+		return LW_FAILURE;
+	type = gserialized1_get_type(g);
 
 	/* Peeking doesn't help if you already have a box or are geodetic */
 	if ( G1FLAGS_GET_GEODETIC(g->gflags) || G1FLAGS_GET_BBOX(g->gflags) )
@@ -491,11 +735,14 @@ gserialized1_copy_point(double *dptr, lwflags_t flags, POINT4D *out_point)
 int
 gserialized1_peek_first_point(const GSERIALIZED *g, POINT4D *out_point)
 {
-	uint8_t *geometry_start = ((uint8_t *)g->data);
-	if (gserialized1_has_bbox(g))
-	{
-		geometry_start += gserialized1_box_size(g);
-	}
+	uint8_t *geometry_start = NULL;
+	uint8_t *geometry_end = NULL;
+
+	if (gserialized1_payload_bounds(g, &geometry_start, &geometry_end) == LW_FAILURE)
+		return LW_FAILURE;
+	if (gserialized1_validate_geometry_buffer(geometry_start, geometry_end, gserialized1_get_lwflags(g), NULL) ==
+	    LW_FAILURE)
+		return LW_FAILURE;
 
 	uint32_t isEmpty = (((uint32_t *)geometry_start)[1]) == 0;
 	if (isEmpty)
@@ -1454,14 +1701,19 @@ LWGEOM* lwgeom_from_gserialized1(const GSERIALIZED *g)
 	assert(g);
 
 	srid = gserialized1_get_srid(g);
-	lwtype = gserialized1_get_type(g);
 	lwflags = gserialized1_get_lwflags(g);
 
-	LWDEBUGF(4, "Got type %d (%s), srid=%d", lwtype, lwtype_name(lwtype), srid);
+	if (gserialized1_payload_bounds(g, &data_ptr, NULL) == LW_FAILURE)
+	{
+		lwerror("%s: invalid GSERIALIZED header size", __func__);
+		return NULL;
+	}
+	if (gserialized1_validate_geometry_buffer(
+		data_ptr, (uint8_t *)g + gserialized1_buffer_size(g), lwflags, NULL) == LW_FAILURE)
+		return NULL;
+	lwtype = gserialized1_get_type(g);
 
-	data_ptr = (uint8_t*)g->data;
-	if (FLAGS_GET_BBOX(lwflags))
-		data_ptr += gbox_serialized_size(lwflags);
+	LWDEBUGF(4, "Got type %d (%s), srid=%d", lwtype, lwtype_name(lwtype), srid);
 
 	lwgeom = lwgeom_from_gserialized1_buffer(data_ptr, lwflags, &size);
 
@@ -1495,6 +1747,8 @@ const float * gserialized1_get_float_box_p(const GSERIALIZED *g, size_t *ndims)
 		*ndims = G1FLAGS_NDIMS_BOX(g->gflags);
 	if (!g) return NULL;
 	if (!G1FLAGS_GET_BBOX(g->gflags)) return NULL;
+	if (gserialized1_buffer_size(g) < offsetof(GSERIALIZED, data) + gserialized1_box_size(g))
+		return NULL;
 	return (const float *)(g->data);
 }
 
@@ -1604,4 +1858,3 @@ GSERIALIZED* gserialized1_drop_gbox(GSERIALIZED *g)
 
 	return g_out;
 }
-
diff --git a/liblwgeom/gserialized2.c b/liblwgeom/gserialized2.c
index fdc0b3eaa5..3281e0b1e9 100644
--- a/liblwgeom/gserialized2.c
+++ b/liblwgeom/gserialized2.c
@@ -48,18 +48,43 @@
 #include "gserialized2.h"
 
 #include <stddef.h>
+#if defined(__has_feature)
+#if __has_feature(address_sanitizer)
+#define POSTGIS_ASAN_ALLOCATOR_SIZE 1
+#endif
+#endif
+#if defined(__SANITIZE_ADDRESS__)
+#define POSTGIS_ASAN_ALLOCATOR_SIZE 1
+#endif
+#if defined(POSTGIS_ASAN_ALLOCATOR_SIZE)
+#include <sanitizer/allocator_interface.h>
+#endif
 
 /***********************************************************************
 * GSERIALIZED metadata utility functions.
 */
 
 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 size_t
+gserialized2_buffer_size(const GSERIALIZED *g)
+{
+	size_t gsize = LWSIZE_GET(g->size);
+#if defined(POSTGIS_ASAN_ALLOCATOR_SIZE)
+	size_t allocated_size = __sanitizer_get_allocated_size(g);
+	if (allocated_size > 0 && allocated_size < gsize)
+		return allocated_size;
+#endif
+	return gsize;
+}
 
 lwflags_t gserialized2_get_lwflags(const GSERIALIZED *g)
 {
 	lwflags_t lwflags = 0;
 	uint8_t gflags = g->gflags;
+	size_t gsize = gserialized2_buffer_size(g);
 	FLAGS_SET_Z(lwflags, G2FLAGS_GET_Z(gflags));
 	FLAGS_SET_M(lwflags, G2FLAGS_GET_M(gflags));
 	FLAGS_SET_BBOX(lwflags, G2FLAGS_GET_BBOX(gflags));
@@ -67,6 +92,11 @@ lwflags_t gserialized2_get_lwflags(const GSERIALIZED *g)
 	if (G2FLAGS_GET_EXTENDED(gflags))
 	{
 		uint64_t xflags = 0;
+		if (gsize < offsetof(GSERIALIZED, data) + sizeof(uint64_t))
+		{
+			lwerror("%s: GSERIALIZED too small for extended flags", __func__);
+			return lwflags;
+		}
 		memcpy(&xflags, g->data, sizeof(uint64_t));
 		FLAGS_SET_SOLID(lwflags, xflags & G2FLAG_X_SOLID);
 	}
@@ -185,24 +215,31 @@ uint32_t gserialized2_max_header_size(void)
 uint32_t gserialized2_get_type(const GSERIALIZED *g)
 {
 	uint8_t *ptr = gserialized2_get_geometry_p(g);
+	size_t hsz = gserialized2_header_size(g);
+	size_t gsize = gserialized2_buffer_size(g);
+	if (gsize < hsz + sizeof(uint32_t))
+	{
+		lwerror("%s: GSERIALIZED too small for geometry type", __func__);
+		return 0;
+	}
 	return *((uint32_t*)(ptr));
 }
 
 int32_t gserialized2_get_srid(const GSERIALIZED *g)
 {
-	int32_t srid = 0;
-	srid = srid | (g->srid[0] << 16);
-	srid = srid | (g->srid[1] << 8);
-	srid = srid | (g->srid[2]);
-	/* Only the first 21 bits are set. Slide up and back to pull
-	   the negative bits down, if we need them. */
-	srid = (srid<<11)>>11;
+	uint32_t srid = 0;
+	srid = srid | ((uint32_t)g->srid[0] << 16);
+	srid = srid | ((uint32_t)g->srid[1] << 8);
+	srid = srid | (uint32_t)g->srid[2];
+	/* Only the first 21 bits are set. Sign-extend without signed shift UB. */
+	if (srid & 0x00100000)
+		srid |= 0xFFE00000;
 
 	/* 0 is our internal unknown value. We'll map back and forth here for now */
 	if (srid == 0)
 		return SRID_UNKNOWN;
 	else
-		return srid;
+		return (int32_t)srid;
 }
 
 void gserialized2_set_srid(GSERIALIZED *g, int32_t srid)
@@ -221,6 +258,235 @@ void gserialized2_set_srid(GSERIALIZED *g, int32_t srid)
 	g->srid[2] = (srid & 0x000000FF);
 }
 
+static int
+gserialized2_range_available(const uint8_t *ptr, const uint8_t *end, size_t len)
+{
+	return ptr <= end && len <= (size_t)(end - ptr);
+}
+
+static int
+gserialized2_checked_mul(size_t a, size_t b, size_t *out)
+{
+	if (a != 0 && b > SIZE_MAX / a)
+		return LW_FAILURE;
+	*out = a * b;
+	return LW_SUCCESS;
+}
+
+static int
+gserialized2_checked_add(size_t a, size_t b, size_t *out)
+{
+	if (b > SIZE_MAX - a)
+		return LW_FAILURE;
+	*out = a + b;
+	return LW_SUCCESS;
+}
+
+static int
+gserialized2_payload_bounds(const GSERIALIZED *g, uint8_t **start, uint8_t **end)
+{
+	size_t hsz;
+	size_t gsize;
+
+	if (!g)
+		return LW_FAILURE;
+
+	hsz = gserialized2_header_size(g);
+	gsize = gserialized2_buffer_size(g);
+	if (gsize < hsz)
+		return LW_FAILURE;
+
+	if (start)
+		*start = (uint8_t *)g + hsz;
+	if (end)
+		*end = (uint8_t *)g + gsize;
+	return LW_SUCCESS;
+}
+
+static uint32_t
+gserialized2_read_uint32_checked(uint8_t *ptr, uint8_t *end, const char *field)
+{
+	uint32_t value = 0;
+	if (!gserialized2_range_available(ptr, end, sizeof(uint32_t)))
+	{
+		lwerror("%s: GSERIALIZED too small for %s", __func__, field);
+		return 0;
+	}
+	memcpy(&value, ptr, sizeof(uint32_t));
+	return value;
+}
+
+static int
+gserialized2_pointarray_payload_size(uint32_t npoints, lwflags_t lwflags, size_t *nbytes)
+{
+	if (gserialized2_checked_mul((size_t)npoints, sizeof(double) * FLAGS_NDIMS(lwflags), nbytes) == LW_FAILURE)
+	{
+		lwerror("%s: GSERIALIZED point count overflows payload size", __func__);
+		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)
+{
+	uint32_t type, count;
+	size_t consumed = 0;
+
+	if (!gserialized2_range_available(data_ptr, data_end, 2 * sizeof(uint32_t)))
+	{
+		lwerror("%s: GSERIALIZED geometry payload is too short", __func__);
+		return LW_FAILURE;
+	}
+
+	type = gserialized2_read_uint32_checked(data_ptr, data_end, "geometry type");
+	count = gserialized2_read_uint32_checked(data_ptr + sizeof(uint32_t), data_end, "geometry count");
+
+	switch (type)
+	{
+	case POINTTYPE:
+		if (count > 1)
+		{
+			lwerror("%s: invalid point count %u", __func__, count);
+			return LW_FAILURE;
+		}
+		/* fall through */
+	case LINETYPE:
+	case CIRCSTRINGTYPE:
+	case TRIANGLETYPE: {
+		size_t point_bytes;
+		if (gserialized2_pointarray_payload_size(count, lwflags, &point_bytes) == LW_FAILURE)
+			return LW_FAILURE;
+		consumed = 2 * sizeof(uint32_t) + point_bytes;
+		break;
+	}
+
+	case POLYGONTYPE: {
+		size_t ring_counts_size, ring_counts_padded_size, point_bytes_sum = 0;
+		uint8_t *ring_counts = data_ptr + 2 * sizeof(uint32_t);
+		uint32_t i;
+
+		if (gserialized2_checked_mul((size_t)count, sizeof(uint32_t), &ring_counts_size) == LW_FAILURE)
+		{
+			lwerror("%s: GSERIALIZED ring count overflows payload size", __func__);
+			return LW_FAILURE;
+		}
+		ring_counts_padded_size = ring_counts_size + ((count % 2) ? sizeof(uint32_t) : 0);
+		if (!gserialized2_range_available(ring_counts, data_end, ring_counts_padded_size))
+		{
+			lwerror("%s: GSERIALIZED polygon ring table exceeds payload size", __func__);
+			return LW_FAILURE;
+		}
+
+		for (i = 0; i < count; i++)
+		{
+			size_t ring_point_bytes;
+			uint32_t npoints = gserialized2_read_uint32_checked(
+			    ring_counts + i * sizeof(uint32_t), data_end, "ring point count");
+			if (gserialized2_pointarray_payload_size(npoints, lwflags, &ring_point_bytes) == LW_FAILURE)
+				return LW_FAILURE;
+			if (gserialized2_checked_add(point_bytes_sum, ring_point_bytes, &point_bytes_sum) == LW_FAILURE)
+			{
+				lwerror("%s: GSERIALIZED polygon coordinate size overflows", __func__);
+				return LW_FAILURE;
+			}
+		}
+		consumed = 2 * sizeof(uint32_t) + ring_counts_padded_size;
+		if (gserialized2_checked_add(consumed, point_bytes_sum, &consumed) == LW_FAILURE)
+		{
+			lwerror("%s: GSERIALIZED polygon size overflows", __func__);
+			return LW_FAILURE;
+		}
+		break;
+	}
+
+	case MULTIPOINTTYPE:
+	case MULTILINETYPE:
+	case MULTIPOLYGONTYPE:
+	case COMPOUNDTYPE:
+	case CURVEPOLYTYPE:
+	case MULTICURVETYPE:
+	case MULTISURFACETYPE:
+	case POLYHEDRALSURFACETYPE:
+	case TINTYPE:
+	case COLLECTIONTYPE: {
+		uint32_t i;
+		uint8_t *subgeom_ptr = data_ptr + 2 * sizeof(uint32_t);
+		lwflags_t subflags = lwflags;
+		FLAGS_SET_BBOX(subflags, 0);
+		for (i = 0; i < count; i++)
+		{
+			size_t subsize = 0;
+			uint32_t subtype =
+			    gserialized2_read_uint32_checked(subgeom_ptr, data_end, "collection subtype");
+			if (!lwcollection_allows_subtype(type, subtype))
+			{
+				lwerror("Invalid subtype (%s) for collection type (%s)",
+					lwtype_name(subtype),
+					lwtype_name(type));
+				return LW_FAILURE;
+			}
+			if (gserialized2_validate_geometry_buffer(subgeom_ptr, data_end, subflags, &subsize) ==
+			    LW_FAILURE)
+				return LW_FAILURE;
+			if (subsize == 0)
+			{
+				lwerror("%s: GSERIALIZED collection member has zero size", __func__);
+				return LW_FAILURE;
+			}
+			subgeom_ptr += subsize;
+		}
+		consumed = (size_t)(subgeom_ptr - data_ptr);
+		break;
+	}
+
+	case NURBSCURVETYPE: {
+		uint32_t nweights, nknots;
+		size_t weight_bytes, knot_bytes, point_bytes;
+		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;
+		}
+		nweights =
+		    gserialized2_read_uint32_checked(data_ptr + 3 * sizeof(uint32_t), data_end, "NURBS weight count");
+		nknots =
+		    gserialized2_read_uint32_checked(data_ptr + 4 * sizeof(uint32_t), data_end, "NURBS knot count");
+		if (gserialized2_checked_mul((size_t)nweights, sizeof(double), &weight_bytes) == LW_FAILURE ||
+		    gserialized2_checked_mul((size_t)nknots, sizeof(double), &knot_bytes) == LW_FAILURE)
+		{
+			lwerror("%s: GSERIALIZED NURBS vector size overflows", __func__);
+			return LW_FAILURE;
+		}
+		if (gserialized2_pointarray_payload_size(count, lwflags, &point_bytes) == LW_FAILURE)
+			return LW_FAILURE;
+		if (gserialized2_checked_add(consumed, weight_bytes, &consumed) == LW_FAILURE ||
+		    gserialized2_checked_add(consumed, knot_bytes, &consumed) == LW_FAILURE ||
+		    gserialized2_checked_add(consumed, point_bytes, &consumed) == LW_FAILURE)
+		{
+			lwerror("%s: GSERIALIZED NURBS size overflows", __func__);
+			return LW_FAILURE;
+		}
+		break;
+	}
+
+	default:
+		lwerror("Unknown geometry type: %d - %s", type, lwtype_name(type));
+		return LW_FAILURE;
+	}
+
+	if (!gserialized2_range_available(data_ptr, data_end, consumed))
+	{
+		lwerror("%s: GSERIALIZED geometry payload exceeds declared size", __func__);
+		return LW_FAILURE;
+	}
+
+	if (size)
+		*size = consumed;
+	return LW_SUCCESS;
+}
+
 static size_t gserialized2_is_empty_recurse(const uint8_t *p, int *isempty);
 static size_t gserialized2_is_empty_recurse(const uint8_t *p, int *isempty)
 {
@@ -285,7 +551,14 @@ static size_t gserialized2_is_empty_recurse(const uint8_t *p, int *isempty)
 int gserialized2_is_empty(const GSERIALIZED *g)
 {
 	int isempty = LW_TRUE;
-	uint8_t *p = gserialized2_get_geometry_p(g);
+	uint8_t *p, *end;
+	if (gserialized2_payload_bounds(g, &p, &end) == LW_FAILURE)
+	{
+		lwerror("%s: invalid GSERIALIZED header size", __func__);
+		return LW_TRUE;
+	}
+	if (gserialized2_validate_geometry_buffer(p, end, gserialized2_get_lwflags(g), NULL) == LW_FAILURE)
+		return LW_TRUE;
 	gserialized2_is_empty_recurse(p, &isempty);
 	return isempty;
 }
@@ -329,6 +602,8 @@ const float * gserialized2_get_float_box_p(const GSERIALIZED *g, size_t *ndims)
 {
 	uint8_t *ptr = (uint8_t*)(g->data);
 	size_t bndims = G2FLAGS_NDIMS_BOX(g->gflags);
+	size_t box_offset = offsetof(GSERIALIZED, data);
+	size_t box_size = gserialized2_box_size(g);
 
 	if (ndims)
 		*ndims = bndims;
@@ -339,7 +614,13 @@ const float * gserialized2_get_float_box_p(const GSERIALIZED *g, size_t *ndims)
 
 	/* Advance past optional extended flags */
 	if (gserialized2_has_extended(g))
+	{
 		ptr += 8;
+		box_offset += 8;
+	}
+
+	if (gserialized2_buffer_size(g) < box_offset + box_size)
+		return NULL;
 
 	return (const float *)(ptr);
 }
@@ -358,6 +639,8 @@ int gserialized2_read_gbox_p(const GSERIALIZED *g, GBOX *gbox)
 	{
 		int i = 0;
 		const float *fbox = gserialized2_get_float_box_p(g, NULL);
+		if (!fbox)
+			return LW_FAILURE;
 		gbox->xmin = fbox[i++];
 		gbox->xmax = fbox[i++];
 		gbox->ymin = fbox[i++];
@@ -393,10 +676,20 @@ int gserialized2_read_gbox_p(const GSERIALIZED *g, GBOX *gbox)
 int
 gserialized2_peek_gbox_p(const GSERIALIZED *g, GBOX *gbox)
 {
-	uint32_t type = gserialized2_get_type(g);
-	uint8_t *geometry_start = gserialized2_get_geometry_p(g);
-	double *dptr = (double *)(geometry_start);
-	int32_t *iptr = (int32_t *)(geometry_start);
+	uint32_t type;
+	uint8_t *geometry_start = NULL;
+	uint8_t *geometry_end = NULL;
+	double *dptr = NULL;
+	int32_t *iptr = NULL;
+
+	if (gserialized2_payload_bounds(g, &geometry_start, &geometry_end) == LW_FAILURE)
+		return LW_FAILURE;
+	if (gserialized2_validate_geometry_buffer(geometry_start, geometry_end, gserialized2_get_lwflags(g), NULL) ==
+	    LW_FAILURE)
+		return LW_FAILURE;
+	type = gserialized2_get_type(g);
+	dptr = (double *)(geometry_start);
+	iptr = (int32_t *)(geometry_start);
 
 	/* Peeking doesn't help if you already have a box or are geodetic */
 	if (G2FLAGS_GET_GEODETIC(g->gflags) || G2FLAGS_GET_BBOX(g->gflags))
@@ -581,7 +874,14 @@ gserialized2_copy_point(double *dptr, lwflags_t flags, POINT4D *out_point)
 int
 gserialized2_peek_first_point(const GSERIALIZED *g, POINT4D *out_point)
 {
-	uint8_t *geometry_start = gserialized2_get_geometry_p(g);
+	uint8_t *geometry_start = NULL;
+	uint8_t *geometry_end = NULL;
+
+	if (gserialized2_payload_bounds(g, &geometry_start, &geometry_end) == LW_FAILURE)
+		return LW_FAILURE;
+	if (gserialized2_validate_geometry_buffer(geometry_start, geometry_end, gserialized2_get_lwflags(g), NULL) ==
+	    LW_FAILURE)
+		return LW_FAILURE;
 
 	uint32_t isEmpty = (((uint32_t *)geometry_start)[1]) == 0;
 	if (isEmpty)
@@ -1540,22 +1840,19 @@ LWGEOM* lwgeom_from_gserialized2(const GSERIALIZED *g)
 	assert(g);
 
 	srid = gserialized2_get_srid(g);
-	lwtype = gserialized2_get_type(g);
 	lwflags = gserialized2_get_lwflags(g);
 
-	LWDEBUGF(4, "Got type %d (%s), srid=%d", lwtype, lwtype_name(lwtype), srid);
-
-	data_ptr = (uint8_t*)g->data;
-
-	/* Skip optional flags */
-	if (G2FLAGS_GET_EXTENDED(g->gflags))
+	if (gserialized2_payload_bounds(g, &data_ptr, NULL) == LW_FAILURE)
 	{
-		data_ptr += sizeof(uint64_t);
+		lwerror("%s: invalid GSERIALIZED header size", __func__);
+		return NULL;
 	}
+	if (gserialized2_validate_geometry_buffer(
+		data_ptr, (uint8_t *)g + gserialized2_buffer_size(g), lwflags, NULL) == LW_FAILURE)
+		return NULL;
+	lwtype = gserialized2_get_type(g);
 
-	/* Skip over optional bounding box */
-	if (FLAGS_GET_BBOX(lwflags))
-		data_ptr += gbox_serialized_size(lwflags);
+	LWDEBUGF(4, "Got type %d (%s), srid=%d", lwtype, lwtype_name(lwtype), srid);
 
 	lwgeom = lwgeom_from_gserialized2_buffer(data_ptr, lwflags, &size, srid);
 

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

Summary of changes:
 NEWS                                  |   6 +
 doc/reference_processing.xml          |   1 +
 liblwgeom/cunit/cu_gserialized1.c     |  42 +++++
 liblwgeom/cunit/cu_gserialized2.c     | 131 ++++++++++++++
 liblwgeom/gserialized1.c              | 301 +++++++++++++++++++++++++++++---
 liblwgeom/gserialized2.c              | 316 +++++++++++++++++++++++++++++++---
 postgis/lwgeom_geos.c                 |  47 ++++-
 raster/rt_pg/rtpg_raster_properties.c |   8 +-
 regress/core/geos39.sql               |   6 +
 regress/core/geos39_expected          |   1 +
 10 files changed, 804 insertions(+), 55 deletions(-)


hooks/post-receive
-- 
PostGIS


More information about the postgis-tickets mailing list