[SCM] PostGIS branch master updated. 3.7.0beta1-69-g031ce41879

git at osgeo.org git at osgeo.org
Mon Jul 27 11:11:08 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  031ce41879e6635874edcfdcc925da55ae5c76fc (commit)
       via  c1f681540c2e20189815f6eabbc6edfb31bf3b1f (commit)
      from  bd7aa23a5c9e357f4b5041fc5498a4f015478f92 (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 031ce41879e6635874edcfdcc925da55ae5c76fc
Merge: bd7aa23a5c c1f681540c
Author: Darafei Praliaskouski <komzpa at gmail.com>
Date:   Mon Jul 27 11:11:07 2026 -0700

    Merge pull request 'Harden GSERIALIZED readers against malformed payload counts' (!522) from Komzpa/postgis:codex/gserialized-reader-harden-20260726 into master
    
    Reviewed-on: https://gitea.osgeo.org/postgis/postgis/pulls/522


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

    Harden GSERIALIZED payload readers

diff --git a/NEWS b/NEWS
index 6d3526076a..d1961a513f 100644
--- a/NEWS
+++ b/NEWS
@@ -16,6 +16,8 @@ These are only changes since 3.7.0beta1.
           spend unbounded time on them (Darafei Praliaskouski)
  - GT-484, Use column-level SELECT privileges for metadata views
           (Arthur Bazin)
+ - GT-521, Reject malformed GSERIALIZED payload counts before deserializing
+          geometry data (Darafei Praliaskouski)
 
 * Enhancements *
 
diff --git a/liblwgeom/cunit/cu_gserialized1.c b/liblwgeom/cunit/cu_gserialized1.c
index 545b83a5bb..2c215f1e22 100644
--- a/liblwgeom/cunit/cu_gserialized1.c
+++ b/liblwgeom/cunit/cu_gserialized1.c
@@ -1228,6 +1228,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.
 */
@@ -1261,4 +1302,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 6a34cf1f6b..4528a790f2 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 08962ae876..813098c77a 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)
 {
@@ -292,7 +558,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;
 }
@@ -340,13 +613,21 @@ 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;
 
 	/* 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);
 }
@@ -366,6 +647,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++];
@@ -401,10 +684,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))
@@ -589,7 +882,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)
@@ -2077,22 +2377,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                              |   2 +
 liblwgeom/cunit/cu_gserialized1.c |  42 +++++
 liblwgeom/cunit/cu_gserialized2.c | 131 ++++++++++++++
 liblwgeom/gserialized1.c          | 301 ++++++++++++++++++++++++++++++---
 liblwgeom/gserialized2.c          | 347 +++++++++++++++++++++++++++++++++++---
 5 files changed, 774 insertions(+), 49 deletions(-)


hooks/post-receive
-- 
PostGIS


More information about the postgis-tickets mailing list