[SCM] PostGIS branch master updated. 3.6.0rc2-621-g09b3ea63d

git at osgeo.org git at osgeo.org
Fri Jun 19 11:58:11 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  09b3ea63d5ac7db56787588c618cb7d4149f95a2 (commit)
      from  b8c7b014233d32d61b75dee622fc76c215949427 (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 09b3ea63d5ac7db56787588c618cb7d4149f95a2
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Fri Jun 19 22:56:27 2026 +0400

    liblwgeom: harden malformed WKB and TWKB parsing
    
    Bound recursive TWKB collection descent to match the WKB parser, reject TWKB delta-coordinate accumulation that would overflow int64_t, and reject malformed WKB NURBSCURVE control-point counts before allocating point and weight arrays.
    
    References https://oss-fuzz.com/testcase-detail/5121296892231680
    
    References https://oss-fuzz.com/testcase-detail/4803520482836480
    
    References https://oss-fuzz.com/testcase-detail/5155658945855488
    
    Closes https://github.com/postgis/postgis/pull/985

diff --git a/NEWS b/NEWS
index 080ac74fd..0788a58d2 100644
--- a/NEWS
+++ b/NEWS
@@ -62,6 +62,8 @@ To take advantage of all postgis_sfcgal extension features SFCGAL 2.3+ is needed
 
 * Bug Fixes *
 
+ - Fix WKB and TWKB parser resource exhaustion on malformed input
+          (Darafei Praliaskouski)
  - #6083, Pass configured dependency include paths to fuzzer smoke builds
           (Darafei Praliaskouski)
  - #3103, Add regression coverage for exact-schema find_srid and
diff --git a/liblwgeom/cunit/cu_in_twkb.c b/liblwgeom/cunit/cu_in_twkb.c
index 5fb4197f0..71c38585d 100644
--- a/liblwgeom/cunit/cu_in_twkb.c
+++ b/liblwgeom/cunit/cu_in_twkb.c
@@ -13,10 +13,12 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <limits.h>
 #include "CUnit/Basic.h"
 
 #include "liblwgeom_internal.h"
 #include "cu_tester.h"
+#include "varint.h"
 
 /*
 ** Global variable to hold TWKB strings
@@ -306,6 +308,82 @@ test_twkb_in_count_exceeds_payload(void)
 	cu_error_msg_reset();
 }
 
+static void
+test_twkb_in_deep_collection(void)
+{
+	const size_t ngeoms = 201;
+	const size_t twkb_size = ngeoms * 3 + 2;
+	uint8_t *twkb = lwalloc(twkb_size);
+	LWGEOM *geom;
+	size_t i;
+
+	for (i = 0; i < ngeoms; i++)
+	{
+		/* GEOMETRYCOLLECTION with default precision and one child. */
+		twkb[3 * i] = 0x07;
+		twkb[3 * i + 1] = 0x00;
+		twkb[3 * i + 2] = 0x01;
+	}
+	twkb[3 * ngeoms] = 0x01;     /* POINT with default precision. */
+	twkb[3 * ngeoms + 1] = 0x10; /* Empty geometry. */
+
+	cu_error_msg_reset();
+
+	geom = lwgeom_from_twkb(twkb, twkb_size, LW_PARSER_CHECK_NONE);
+
+	/* Recursive collection parsing must reject hostile nesting before the C
+	 * stack becomes the effective input validator.
+	 */
+	ASSERT_STRING_EQUAL(cu_error_msg, "Geometry has too many chained collections");
+	CU_ASSERT_PTR_NULL(geom);
+	lwfree(twkb);
+	cu_error_msg_reset();
+}
+
+static void
+test_twkb_in_coordinate_overflow(void)
+{
+	const uint8_t twkb[] = {0x03, 0x00, 0x16, 0x16, 0x16, 0x16, 0x16, 0x2f, 0x00, 0x00, 0x3d, 0x00, 0x00,
+				0x00, 0x00, 0x00, 0x16, 0x16, 0x16, 0x16, 0x16, 0x23, 0x06, 0x02, 0x7e, 0x84,
+				0x83, 0x84, 0x84, 0x84, 0x84, 0xae, 0xee, 0xff, 0x01, 0xf9, 0xff, 0xff, 0xff,
+				0xff, 0xef, 0x16, 0x16, 0xff, 0x16, 0x16, 0x16, 0x23, 0x06, 0x02, 0x7e, 0x84,
+				0x83, 0x84, 0x84, 0x84, 0x84, 0xae, 0xee, 0xff, 0x01, 0xf9, 0xff, 0xff, 0xff,
+				0xff, 0xef, 0xff, 0xff, 0x01, 0xff, 0x02, 0x00, 0x79, 0xb0};
+	LWGEOM *geom;
+
+	cu_error_msg_reset();
+
+	geom = lwgeom_from_twkb(twkb, sizeof(twkb), LW_PARSER_CHECK_NONE);
+
+	/* Delta-encoded coordinates must not rely on signed integer overflow to
+	 * wrap hostile inputs into the int64_t accumulator range.
+	 */
+	ASSERT_STRING_EQUAL(cu_error_msg, "twkb_parse_state_accum_coord: TWKB coordinate delta overflows int64_t");
+	CU_ASSERT_PTR_NULL(geom);
+	cu_error_msg_reset();
+}
+
+static void
+test_twkb_in_linestring_coordinate_overflow(void)
+{
+	uint8_t twkb[64] = {0x02, 0x00, 0x02};
+	uint8_t *pos = twkb + 3;
+	LWGEOM *geom;
+
+	pos += varint_s64_encode_buf(INT64_MAX, pos);
+	pos += varint_s64_encode_buf(0, pos);
+	pos += varint_s64_encode_buf(1, pos);
+	pos += varint_s64_encode_buf(0, pos);
+
+	cu_error_msg_reset();
+
+	geom = lwgeom_from_twkb(twkb, (size_t)(pos - twkb), LW_PARSER_CHECK_NONE);
+
+	ASSERT_STRING_EQUAL(cu_error_msg, "twkb_parse_state_accum_coord: TWKB coordinate delta overflows int64_t");
+	CU_ASSERT_PTR_NULL(geom);
+	cu_error_msg_reset();
+}
+
 /*
 ** Used by test harness to register the tests in this file.
 */
@@ -324,4 +402,7 @@ void twkb_in_suite_setup(void)
 	PG_ADD_TEST(suite, test_twkb_in_truncated_extended_dims);
 	PG_ADD_TEST(suite, test_twkb_in_overlong_varint);
 	PG_ADD_TEST(suite, test_twkb_in_count_exceeds_payload);
+	PG_ADD_TEST(suite, test_twkb_in_deep_collection);
+	PG_ADD_TEST(suite, test_twkb_in_coordinate_overflow);
+	PG_ADD_TEST(suite, test_twkb_in_linestring_coordinate_overflow);
 }
diff --git a/liblwgeom/cunit/cu_in_wkb.c b/liblwgeom/cunit/cu_in_wkb.c
index c855dccf3..2b9066ae8 100644
--- a/liblwgeom/cunit/cu_in_wkb.c
+++ b/liblwgeom/cunit/cu_in_wkb.c
@@ -283,6 +283,22 @@ test_wkb_fuzz(void)
 	lwfree(wkb5);
 }
 
+static void
+test_wkb_in_nurbscurve_count_exceeds_payload(void)
+{
+	const uint8_t wkb[] = {
+	    0x01, 0x15, 0x00, 0x00, 0x40, 0x01, 0x00, 0x00, 0x00, 0xc4, 0x0c, 0xb7, 0x07, 0x21, 0xc7};
+	LWGEOM *geom;
+
+	cu_error_msg_reset();
+
+	geom = lwgeom_from_wkb(wkb, sizeof(wkb), LW_PARSER_CHECK_NONE);
+
+	ASSERT_STRING_EQUAL(cu_error_msg, "WKB structure does not match expected size!");
+	CU_ASSERT_PTR_NULL(geom);
+	cu_error_msg_reset();
+}
+
 /*
 ** Used by test harness to register the tests in this file.
 */
@@ -304,4 +320,5 @@ void wkb_in_suite_setup(void)
 	PG_ADD_TEST(suite, test_wkb_in_multisurface);
 	PG_ADD_TEST(suite, test_wkb_in_malformed);
 	PG_ADD_TEST(suite, test_wkb_fuzz);
+	PG_ADD_TEST(suite, test_wkb_in_nurbscurve_count_exceeds_payload);
 }
diff --git a/liblwgeom/lwin_twkb.c b/liblwgeom/lwin_twkb.c
index 1262dd999..04261848d 100644
--- a/liblwgeom/lwin_twkb.c
+++ b/liblwgeom/lwin_twkb.c
@@ -24,11 +24,14 @@
  **********************************************************************/
 
 #include <math.h>
+#include <limits.h>
 #include "liblwgeom_internal.h"
 #include "lwgeom_log.h"
 #include "varint.h"
 
 #define TWKB_IN_MAXCOORDS 4
+/** Max depth in a geometry. Matches the WKB parser recursion limit. */
+#define LW_PARSER_MAX_DEPTH 200
 
 /**
 * Used for passing the parse state between the parsing functions.
@@ -42,6 +45,8 @@ typedef struct
 
 	uint32_t check; /* Simple validity checks on geometries */
 	uint32_t lwtype; /* Current type we are handling */
+	uint8_t depth;   /* Current recursion level, to prevent stack overflows */
+	uint8_t error;   /* A hard parse error was found */
 
 	uint8_t has_bbox;
 	uint8_t has_size;
@@ -157,6 +162,23 @@ twkb_parse_state_has_min_bytes(twkb_parse_state *s, uint32_t count, size_t min_b
 	return LW_TRUE;
 }
 
+static inline int
+twkb_parse_state_accum_coord(twkb_parse_state *s, int coord_index)
+{
+	const int64_t delta = twkb_parse_state_varint(s);
+
+	if ((delta > 0 && s->coords[coord_index] > INT64_MAX - delta) ||
+	    (delta < 0 && s->coords[coord_index] < INT64_MIN - delta))
+	{
+		s->error = LW_TRUE;
+		lwerror("%s: TWKB coordinate delta overflows int64_t", __func__);
+		return LW_FALSE;
+	}
+
+	s->coords[coord_index] += delta;
+	return LW_TRUE;
+}
+
 static uint32_t lwtype_from_twkb_type(uint8_t twkb_type)
 {
 	switch (twkb_type)
@@ -211,7 +233,9 @@ static POINTARRAY* ptarray_from_twkb_state(twkb_parse_state *s, uint32_t npoints
 	POINTARRAY *pa = NULL;
 	uint32_t ndims = s->ndims;
 	uint32_t i;
+	uint32_t j;
 	double *dlist;
+	double factors[TWKB_IN_MAXCOORDS];
 
 	LWDEBUG(2,"Entering ptarray_from_twkb_state");
 	LWDEBUGF(4,"Pointarray has %d points", npoints);
@@ -223,32 +247,26 @@ static POINTARRAY* ptarray_from_twkb_state(twkb_parse_state *s, uint32_t npoints
 	if (!twkb_parse_state_has_min_bytes(s, npoints, s->ndims))
 		return NULL;
 
+	factors[0] = s->factor;
+	factors[1] = s->factor;
+	j = 2;
+	if (s->has_z)
+		factors[j++] = s->factor_z;
+	if (s->has_m)
+		factors[j++] = s->factor_m;
+
 	pa = ptarray_construct(s->has_z, s->has_m, npoints);
 	dlist = (double*)(pa->serialized_pointlist);
 	for( i = 0; i < npoints; i++ )
 	{
-		int j = 0;
-		/* X */
-		s->coords[j] += twkb_parse_state_varint(s);
-		dlist[ndims*i + j] = s->coords[j] / s->factor;
-		j++;
-		/* Y */
-		s->coords[j] += twkb_parse_state_varint(s);
-		dlist[ndims*i + j] = s->coords[j] / s->factor;
-		j++;
-		/* Z */
-		if ( s->has_z )
+		for (j = 0; j < ndims; j++)
 		{
-			s->coords[j] += twkb_parse_state_varint(s);
-			dlist[ndims*i + j] = s->coords[j] / s->factor_z;
-			j++;
-		}
-		/* M */
-		if ( s->has_m )
-		{
-			s->coords[j] += twkb_parse_state_varint(s);
-			dlist[ndims*i + j] = s->coords[j] / s->factor_m;
-			j++;
+			if (!twkb_parse_state_accum_coord(s, j))
+			{
+				ptarray_free(pa);
+				return NULL;
+			}
+			dlist[ndims * i + j] = s->coords[j] / factors[j];
 		}
 	}
 
@@ -295,7 +313,11 @@ static LWLINE* lwline_from_twkb_state(twkb_parse_state *s)
 	pa = ptarray_from_twkb_state(s, npoints);
 
 	if( pa == NULL )
+	{
+		if (s->error)
+			return NULL;
 		return lwline_construct_empty(SRID_UNKNOWN, s->has_z, s->has_m);
+	}
 
 	if( s->check & LW_PARSER_CHECK_MINPOINTS && pa->npoints < 2 )
 	{
@@ -343,7 +365,14 @@ static LWPOLY* lwpoly_from_twkb_state(twkb_parse_state *s)
 
 		/* Skip empty rings */
 		if( pa == NULL )
+		{
+			if (s->error)
+			{
+				lwpoly_free(poly);
+				return NULL;
+			}
 			continue;
+		}
 
 		/* Force first and last points to be the same. */
 		if( ! ptarray_is_closed_2d(pa) )
@@ -515,6 +544,7 @@ static LWCOLLECTION* lwcollection_from_twkb_state(twkb_parse_state *s)
 	uint32_t ngeoms, i;
 	LWGEOM *geom = NULL;
 	LWCOLLECTION *col = lwcollection_construct_empty(s->lwtype, SRID_UNKNOWN, s->has_z, s->has_m);
+	uint8_t start_depth = s->depth;
 
 	LWDEBUG(2,"Entering lwcollection_from_twkb_state");
 
@@ -539,16 +569,28 @@ static LWCOLLECTION* lwcollection_from_twkb_state(twkb_parse_state *s)
 	if (!twkb_parse_state_has_min_bytes(s, ngeoms, 2))
 		return col;
 
+	s->depth++;
+	if (s->depth >= LW_PARSER_MAX_DEPTH)
+	{
+		lwcollection_free(col);
+		lwerror("Geometry has too many chained collections");
+		s->depth = start_depth;
+		return NULL;
+	}
+
 	for ( i = 0; i < ngeoms; i++ )
 	{
 		geom = lwgeom_from_twkb_state(s);
-		if ( lwcollection_add_lwgeom(col, geom) == NULL )
+		if (!geom || lwcollection_add_lwgeom(col, geom) == NULL)
 		{
-			lwerror("Unable to add geometry (%p) to collection (%p)", (void *) geom, (void *) col);
+			lwgeom_free(geom);
+			lwcollection_free(col);
+			s->depth = start_depth;
 			return NULL;
 		}
 	}
 
+	s->depth--;
 
 	return col;
 }
@@ -702,7 +744,7 @@ LWGEOM* lwgeom_from_twkb_state(twkb_parse_state *s)
 			break;
 	}
 
-	if ( has_bbox )
+	if (has_bbox && geom)
 		geom->bbox = gbox_clone(&bbox);
 
 	return geom;
diff --git a/liblwgeom/lwin_wkb.c b/liblwgeom/lwin_wkb.c
index 762d5cd3a..83acb14c3 100644
--- a/liblwgeom/lwin_wkb.c
+++ b/liblwgeom/lwin_wkb.c
@@ -831,6 +831,10 @@ static LWNURBSCURVE* lwnurbscurve_from_wkb_state(wkb_parse_state *s)
 		        degree, (unsigned long long)degree + 1, npoints);
 		return NULL;
 	}
+	const size_t min_point_size = 2 + (2 + (s->has_z ? 1 : 0) + (s->has_m ? 1 : 0)) * WKB_DOUBLE_SIZE;
+	wkb_parse_state_check(s, (size_t)npoints * min_point_size);
+	if (s->error)
+		return NULL;
 
 	/* Initialize points array */
 	if (npoints > 0) {

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

Summary of changes:
 NEWS                         |  2 +
 liblwgeom/cunit/cu_in_twkb.c | 81 +++++++++++++++++++++++++++++++++++++++
 liblwgeom/cunit/cu_in_wkb.c  | 17 +++++++++
 liblwgeom/lwin_twkb.c        | 90 ++++++++++++++++++++++++++++++++------------
 liblwgeom/lwin_wkb.c         |  4 ++
 5 files changed, 170 insertions(+), 24 deletions(-)


hooks/post-receive
-- 
PostGIS


More information about the postgis-tickets mailing list