[SCM] PostGIS branch stable-3.3 updated. 3.3.10-113-g92a92be81a

git at osgeo.org git at osgeo.org
Sun Aug 9 12:20:13 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.3 has been updated
       via  92a92be81af176ba24e16378be898db313704c64 (commit)
       via  19aa7632d4db0fc407eefd8af6894bf47cafdd69 (commit)
       via  24b8467db6f93f2b44cdc40c95a0f359e48ce667 (commit)
       via  83bd420abaffd72fea9f51b5ab773584edf9b176 (commit)
       via  0afb55090d4a4e1c57583f40362b570e4b418766 (commit)
       via  8d7a8fe3aba05862f04e21cdc776a63811164f8c (commit)
       via  aec36b90d5e924b875c3aea34a1e38f44be36946 (commit)
       via  67d7524081e5b0bc06a21ba395f8d3550aa7ca5d (commit)
       via  0f5c7300cd7d53ec67910d0af69bab3e7860ac97 (commit)
       via  bd670a94f89f730cd66802d9b919c78cedf160b0 (commit)
      from  4add825b2b0dc97dcbe24b48efa39f86d46d9599 (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 92a92be81af176ba24e16378be898db313704c64
Merge: 4add825b2b 19aa7632d4
Author: Darafei Praliaskouski <komzpa at gmail.com>
Date:   Sun Aug 9 12:20:12 2026 -0700

    Merge pull request 'Backport correctness and input-safety fixes to stable-3.3' (!687) from Komzpa/postgis:fix/stable-3.3-backpatch-sweep-20260810 into stable-3.3
    
    Malformed FlatGeobuf headers or features can contain a truncated size-prefixed buffer. This backport checks that the prefix is present and that the declared payload fits in the supplied buffer before decoding it; verification now covers the complete prefixed buffer.
    
    It also rejects truncated encoded-polyline varints before any byte is read past the input, frees temporary geometries in the stable `ST_DWithin` implementation, and stops generated extension upgrade scripts from running `ANALYZE` inside their transaction.
    
    `geometry_columns` can retain a stale relation row after a relation is dropped or SELECT privilege is revoked. This adds the visibility-safe re-resolution and focused regression from [Trac ticket 6038](https://trac.osgeo.org/postgis/ticket/6038).
    
    `BOX2D_out` and `BOX2D_expand` now copy only the materialized 2D payload instead of reading past it; the focused regression comes from [Trac ticket 6109](https://trac.osgeo.org/postgis/ticket/6109).
    
    `ST_Normalize` now uses the LWGEOM fallback for TIN and PolyhedralSurface inputs, preserving TRIANGLE children instead of letting GEOS conversion change their type.
    
    Sources:
    
    - https://gitea.osgeo.org/postgis/postgis/commit/0a7c72a9102033c8b6ddb4f3875cc0741d3baa0a
    - https://gitea.osgeo.org/postgis/postgis/commit/491813e413afff546779b988e79ad7eb20559c46
    - https://gitea.osgeo.org/postgis/postgis/commit/94cc593de819c917edc0d0a49c9c2ee76a5f88ce
    - https://gitea.osgeo.org/postgis/postgis/commit/248b014b9805911792e2fcc097ce7c0cde422308
    - https://gitea.osgeo.org/postgis/postgis/commit/6f11ffd051915508ef244399043b484a2de46295
    - https://gitea.osgeo.org/postgis/postgis/commit/31aec6787f974c94ff5fa0180969d73dd1817e14
    - https://gitea.osgeo.org/postgis/postgis/commit/1f6aabb0f4fb5845d31b38d0afc69ea6e466fcef
    - https://gitea.osgeo.org/postgis/postgis/commit/80a8dcabf7d9f0b19abae1d853571473cbd9c075
    
    The FlatGeobuf change is a narrow semantic port because the vendored dependency differs on this branch. The encoded-polyline and upgrade commits retain upstream authorship; `ST_DWithin` is adapted only for its earlier stable source location.
    
    Validation: `git diff --check`; built and staged against PostgreSQL 15; focused `regress/core/regress_management`, `regress/core/box2d`, and `regress/core/normalize` passed the create and upgrade paths with 0 failures.
    
    Reviewed-on: https://gitea.osgeo.org/postgis/postgis/pulls/687


commit 19aa7632d4db0fc407eefd8af6894bf47cafdd69
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Sun Aug 9 22:58:41 2026 +0400

    Fix ST_Normalize TIN child normalization
    
    Squashed manual backport of the surface-normalization fallback and its TIN child correction; excludes unrelated example-test changes.\n\nPorted-from: 1f6aabb0f4fb5845d31b38d0afc69ea6e466fcef\nPorted-from: 80a8dcabf7d9f0b19abae1d853571473cbd9c075
    
    (cherry picked from commit c01443760cbc487739d8b64b8960339df2086643)

diff --git a/NEWS b/NEWS
index f83a7f414a..7c8888226a 100644
--- a/NEWS
+++ b/NEWS
@@ -3,6 +3,8 @@ PostGIS 3.3.11
 
 * Bug Fixes and Enhancements *
 
+ - GT-675, Preserve TRIANGLE children while normalizing malformed TINs
+   (Darafei Praliaskouski)
  - #6038, Avoid stale geometry_columns relation lookups after relations are
    dropped or SELECT privilege is revoked (Darafei Praliaskouski)
  - #6109, Avoid out-of-bounds reads in BOX2D_out and BOX2D_expand
diff --git a/liblwgeom/lwgeom_geos.c b/liblwgeom/lwgeom_geos.c
index 7192ee5031..de7ae2f905 100644
--- a/liblwgeom/lwgeom_geos.c
+++ b/liblwgeom/lwgeom_geos.c
@@ -32,6 +32,7 @@
 
 #include <stdarg.h>
 #include <stdlib.h>
+#include <string.h>
 
 LWTIN* lwtin_from_geos(const GEOSGeometry* geom, uint8_t want3d);
 
@@ -532,6 +533,7 @@ LWGEOM2GEOS(const LWGEOM* lwgeom, uint8_t autofix)
 	case MULTILINETYPE:
 	case MULTIPOLYGONTYPE:
 	case TINTYPE:
+	case POLYHEDRALSURFACETYPE:
 	case COLLECTIONTYPE:
 		if (lwgeom->type == MULTIPOINTTYPE)
 			geostype = GEOS_MULTIPOINT;
@@ -672,6 +674,89 @@ get_result_srid(size_t count, const char* funcname, ...)
 	return srid;
 }
 
+static int
+lwgeom_geos_normalize_unsupported_type(uint8_t type)
+{
+	return type == POLYHEDRALSURFACETYPE || type == TINTYPE;
+}
+
+static int
+lwgeom_geos_normalize_needs_lwgeom(const LWGEOM *geom)
+{
+	uint32_t i;
+	LWCOLLECTION *col;
+
+	if (lwgeom_geos_normalize_unsupported_type(geom->type))
+		return LW_TRUE;
+	if (!lwtype_is_collection(geom->type))
+		return LW_FALSE;
+	col = (LWCOLLECTION *)geom;
+	for (i = 0; i < col->ngeoms; i++)
+		if (lwgeom_geos_normalize_needs_lwgeom(col->geoms[i]))
+			return LW_TRUE;
+	return LW_FALSE;
+}
+
+static int
+lwgeom_compare_by_xdr_ewkb(const void *a, const void *b)
+{
+	const LWGEOM *ga = *(const LWGEOM *const *)a;
+	const LWGEOM *gb = *(const LWGEOM *const *)b;
+	char *wa = lwgeom_to_hexwkb_buffer(ga, WKB_EXTENDED | WKB_XDR);
+	char *wb = lwgeom_to_hexwkb_buffer(gb, WKB_EXTENDED | WKB_XDR);
+	int cmp = strcmp(wa, wb);
+	lwfree(wa);
+	lwfree(wb);
+	return cmp;
+}
+
+static LWGEOM *
+lwgeom_normalized_triangle_or_clone(const LWGEOM *triangle_in, LWGEOM *normalized)
+{
+	LWPOLY *poly;
+	LWTRIANGLE *triangle;
+	POINTARRAY *points;
+	if (normalized && normalized->type == TRIANGLETYPE)
+		return normalized;
+	if (!normalized || normalized->type != POLYGONTYPE)
+	{
+		lwgeom_free(normalized);
+		return lwgeom_clone_deep(triangle_in);
+	}
+	poly = lwgeom_as_lwpoly(normalized);
+	if (poly->nrings != 1 || !poly->rings[0])
+	{
+		lwgeom_free(normalized);
+		return lwgeom_clone_deep(triangle_in);
+	}
+	points = ptarray_clone_deep(poly->rings[0]);
+	triangle = lwtriangle_construct(poly->srid, NULL, points);
+	lwgeom_free(normalized);
+	return lwtriangle_as_lwgeom(triangle);
+}
+
+static LWGEOM *
+lwgeom_normalize_lwgeom(const LWGEOM *geom)
+{
+	uint32_t i;
+	LWCOLLECTION *col;
+	if (!lwtype_is_collection(geom->type))
+		return lwgeom_clone_deep(geom);
+	col = lwcollection_clone_deep((LWCOLLECTION *)geom);
+	for (i = 0; i < col->ngeoms; i++)
+	{
+		LWGEOM *normalized = lwgeom_normalize(col->geoms[i]);
+		if (col->type == TINTYPE && col->geoms[i]->type == TRIANGLETYPE)
+			normalized = lwgeom_normalized_triangle_or_clone(col->geoms[i], normalized);
+		lwgeom_free(col->geoms[i]);
+		col->geoms[i] = normalized;
+	}
+	if (col->ngeoms > 1)
+		qsort(col->geoms, col->ngeoms, sizeof(LWGEOM *), lwgeom_compare_by_xdr_ewkb);
+	lwgeom_drop_bbox((LWGEOM *)col);
+	return (LWGEOM *)col;
+}
+
 LWGEOM*
 lwgeom_normalize(const LWGEOM* geom)
 {
@@ -681,6 +766,8 @@ lwgeom_normalize(const LWGEOM* geom)
 	GEOSGeometry* g;
 
 	if (srid == SRID_INVALID) return NULL;
+	if (lwgeom_geos_normalize_needs_lwgeom(geom))
+		return lwgeom_normalize_lwgeom(geom);
 
 	initGEOS(lwnotice, lwgeom_geos_error);
 
diff --git a/regress/core/normalize.sql b/regress/core/normalize.sql
index 02aac6518b..547b353a44 100644
--- a/regress/core/normalize.sql
+++ b/regress/core/normalize.sql
@@ -5,3 +5,11 @@ select 1, ST_AsText(ST_Normalize(
 select 2, ST_AsText(ST_Normalize(
 'POLYGON((0 10,0 0,10 0,10 10,0 10),(4 2,2 2,2 4,4 4,4 2),(6 8,8 8,8 6,6 6,6 8))'
 ::geometry));
+
+select 3, GeometryType(ST_Normalize('POLYHEDRALSURFACE(((0 0,2 0,0 2,0 0)),((10 10,11 10,10 11,10 10)))'::geometry));
+select 4, GeometryType(ST_Normalize('TIN(((0 0,2 0,0 2,0 0)),((10 10,11 10,10 11,10 10)))'::geometry));
+select 5, GeometryType(ST_GeometryN(ST_Normalize('GEOMETRYCOLLECTION(POLYHEDRALSURFACE(((0 0,2 0,0 2,0 0))))'::geometry), 1));
+with normalized as (
+	select ST_Normalize(ST_GeomFromWKB(decode('0110000000010000000111000000010000000500000000000000000000000000000000000000000000000000f03f0000000000000000000000000000f03f000000000000f03f0000000000000000000000000000f03f00000000000000000000000000000000','hex'))) as geom
+)
+select 6, GeometryType(normalized.geom), GeometryType((dumped).geom) from normalized cross join lateral ST_Dump(normalized.geom) as dumped;
diff --git a/regress/core/normalize_expected b/regress/core/normalize_expected
index 36d32a2f7c..c42004281b 100644
--- a/regress/core/normalize_expected
+++ b/regress/core/normalize_expected
@@ -1,2 +1,6 @@
 1|GEOMETRYCOLLECTION(MULTILINESTRING((2 2,3 3),(0 0,1 1)),POINT(2 3))
 2|POLYGON((0 0,0 10,10 10,10 0,0 0),(6 6,8 6,8 8,6 8,6 6),(2 2,4 2,4 4,2 4,2 2))
+3|POLYHEDRALSURFACE
+4|TIN
+5|POLYHEDRALSURFACE
+6|TIN|TRIANGLE

commit 24b8467db6f93f2b44cdc40c95a0f359e48ce667
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Sun Aug 9 22:56:15 2026 +0400

    NEWS: reference BOX2D out-of-bounds fix

diff --git a/NEWS b/NEWS
index 8984367220..f83a7f414a 100644
--- a/NEWS
+++ b/NEWS
@@ -5,6 +5,8 @@ PostGIS 3.3.11
 
  - #6038, Avoid stale geometry_columns relation lookups after relations are
    dropped or SELECT privilege is revoked (Darafei Praliaskouski)
+ - #6109, Avoid out-of-bounds reads in BOX2D_out and BOX2D_expand
+   (Dennis Tighe, Google)
  - GT-669, Reject truncated size-prefixed header and feature buffers
    before decoding (Darafei Praliaskouski)
  - Stop the extension upgrade script running ANALYZE inside its transaction,

commit 83bd420abaffd72fea9f51b5ab773584edf9b176
Author: Dennis Tighe <dtighe at google.com>
Date:   Wed Jul 29 17:49:01 2026 -0700

    Fix out-of-bounds read in BOX2D_out and BOX2D_expand
    
    box2d is declared with internallength=65 (postgis.sql.in), which is 7
    bytes short of sizeof(GBOX) (72). Copying the full GBOX struct (72 bytes) via
    memcpy in BOX2D_out and BOX2D_expand causes a 7-byte out-of-bounds heap
    read under ASAN. Reference: #6109
    
    This change fixes this by copying only up to offsetof(GBOX, zmin), which contains all
    2D box fields (xmin, xmax, ymin, ymax, flags, SRID) within the 65-byte payload.
    This also memzeros the target buffer out as a defensive measure where it wasn't
    done previously.
    
    Add regression test regress/core/box2d.sql (these fail under ASAN today).
    
    References #6109 for PostGIS 3.7.0
    Closes https://gitea.osgeo.org/postgis/postgis/pulls/628 for PostGIS 3.7.0
    
    (cherry picked from commit 31aec6787f974c94ff5fa0180969d73dd1817e14)

diff --git a/postgis/lwgeom_box.c b/postgis/lwgeom_box.c
index 98b39ed955..f53424507a 100644
--- a/postgis/lwgeom_box.c
+++ b/postgis/lwgeom_box.c
@@ -100,9 +100,15 @@ Datum BOX2D_out(PG_FUNCTION_ARGS)
 	int size = 0;
 
 	GBOX *box = (GBOX *)PG_GETARG_POINTER(0);
-	/* Avoid unaligned access to the gbox struct */
+	/* Avoid unaligned access to the gbox struct.
+	 *
+	 * box2d can be 65 bytes, which is 7 bytes short of sizeof(GBOX) (72)
+	 * so just copy the required part (xy/min, xy/max) and zero the rest
+	 * (matches BOX2d_expand).
+	 */
 	GBOX box_aligned;
-	memcpy(&box_aligned, box, sizeof(GBOX));
+	memset(&box_aligned, 0, sizeof(box_aligned));
+	memcpy(&box_aligned, box, offsetof(GBOX, zmin));
 
 	size = 4;
 	size += lwprint_double(box_aligned.xmin, precision, &tmp[size]);
@@ -387,8 +393,10 @@ PG_FUNCTION_INFO_V1(BOX2D_expand);
 Datum BOX2D_expand(PG_FUNCTION_ARGS)
 {
 	GBOX *box = (GBOX *)PG_GETARG_POINTER(0);
-	GBOX *result = (GBOX *)palloc(sizeof(GBOX));
-	memcpy(result, box, sizeof(GBOX));
+	/* box2d is 65 bytes (7 bytes short of sizeof(GBOX)) and is a purely
+	 * 2D box; copy only the 2D header */
+	GBOX *result = (GBOX *)palloc0(sizeof(GBOX));
+	memcpy(result, box, offsetof(GBOX, zmin));
 
 	if (PG_NARGS() == 2)
 	{
diff --git a/regress/core/box2d.sql b/regress/core/box2d.sql
new file mode 100644
index 0000000000..746f778603
--- /dev/null
+++ b/regress/core/box2d.sql
@@ -0,0 +1,8 @@
+-- box2d output / expand must not read past a materialised box2d value.
+-- see #6109
+SELECT 'out',        ('LINESTRING(0 0,1 1)'::geometry::box2d)::text;
+SELECT 'out_3d',     ('LINESTRING Z (0 0 9,2 3 9)'::geometry::box2d)::text;
+SELECT 'roundtrip',  'BOX(1.5 2.5,3.5 4.5)'::box2d::text;
+SELECT 'extent',     ST_Extent(g)::text FROM (VALUES ('POINT(0 0)'::geometry),('POINT(5 7)'::geometry)) v(g);
+SELECT 'expand_d',   ST_Expand('LINESTRING(0 0,10 10)'::geometry::box2d, 1)::text;
+SELECT 'expand_dxdy',ST_Expand('LINESTRING(0 0,10 10)'::geometry::box2d, 2, 3)::text;
diff --git a/regress/core/box2d_expected b/regress/core/box2d_expected
new file mode 100644
index 0000000000..e6ce8e0b6d
--- /dev/null
+++ b/regress/core/box2d_expected
@@ -0,0 +1,6 @@
+out|BOX(0 0,1 1)
+out_3d|BOX(0 0,2 3)
+roundtrip|BOX(1.5 2.5,3.5 4.5)
+extent|BOX(0 0,5 7)
+expand_d|BOX(-1 -1,11 11)
+expand_dxdy|BOX(-2 -3,12 13)
diff --git a/regress/core/tests.mk.in b/regress/core/tests.mk.in
index 342ca44c13..ad5b5ee07a 100644
--- a/regress/core/tests.mk.in
+++ b/regress/core/tests.mk.in
@@ -31,6 +31,7 @@ TESTS += \
 	$(top_srcdir)/regress/core/bestsrid \
 	$(top_srcdir)/regress/core/binary \
 	$(top_srcdir)/regress/core/boundary \
+	$(top_srcdir)/regress/core/box2d \
 	$(top_srcdir)/regress/core/chaikin \
 	$(top_srcdir)/regress/core/filterm \
 	$(top_srcdir)/regress/core/cluster \

commit 0afb55090d4a4e1c57583f40362b570e4b418766
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Fri Jun 19 23:42:36 2026 +0400

    postgis: avoid stale geometry_columns relation lookups
    
    Filter stale pg_class rows from geometry_columns by re-resolving visible schema-qualified names before exposing metadata, while keeping inaccessible schemas on OID-based privilege checks that do not resolve hidden names. Require the current SELECT privilege result in both visible and hidden paths so stale snapshots do not keep exposing metadata after relation drops or SELECT revokes.
    
    Closes #6038
    
    Closes https://github.com/postgis/postgis/pull/915
    
    (cherry picked from commit 6f11ffd051915508ef244399043b484a2de46295)

diff --git a/NEWS b/NEWS
index 1a6b38ec50..8984367220 100644
--- a/NEWS
+++ b/NEWS
@@ -3,6 +3,8 @@ PostGIS 3.3.11
 
 * Bug Fixes and Enhancements *
 
+ - #6038, Avoid stale geometry_columns relation lookups after relations are
+   dropped or SELECT privilege is revoked (Darafei Praliaskouski)
  - GT-669, Reject truncated size-prefixed header and feature buffers
    before decoding (Darafei Praliaskouski)
  - Stop the extension upgrade script running ANALYZE inside its transaction,
diff --git a/postgis/postgis.sql.in b/postgis/postgis.sql.in
index 54eca5b18c..784cb1ef1e 100644
--- a/postgis/postgis.sql.in
+++ b/postgis/postgis.sql.in
@@ -6333,7 +6333,40 @@ CREATE OR REPLACE VIEW geometry_columns AS
 ) sr ON sr.connamespace = n.oid AND sr.conrelid = c.oid AND (a.attnum = ANY (sr.conkey))
   WHERE (c.relkind = ANY (ARRAY['r'::"char", 'v'::"char", 'm'::"char", 'f'::"char", 'p'::"char"]))
   AND NOT c.relname = 'raster_columns'::name AND t.typname = 'geometry'::name
-  AND NOT pg_is_other_temp_schema(c.relnamespace) AND has_table_privilege(c.oid, 'SELECT'::text);
+  AND NOT pg_is_other_temp_schema(c.relnamespace)
+  -- A stale MVCC snapshot can still see a dropped pg_class row (#6038).
+  -- Re-resolve the qualified relation name only when the schema is visible,
+  -- because to_regclass reports permission errors for inaccessible schemas.
+  -- Without schema visibility, keep the old OID-based privilege semantics.
+  -- Keep the visible-schema SELECT check in the same CASE branch as the
+  -- re-resolution guard so PostgreSQL cannot evaluate it for a stale OID.
+  AND CASE WHEN has_schema_privilege(c.relnamespace, 'USAGE'::text)
+           THEN CASE WHEN pg_catalog.to_regclass(pg_catalog.format('%I.%I', n.nspname, c.relname)) = c.oid
+                     THEN pg_catalog.has_table_privilege(c.oid, 'SELECT')
+                     ELSE false
+                END
+           ELSE pg_catalog.has_table_privilege(c.oid, 'SELECT')
+      END
+  AND (
+    EXISTS (
+      SELECT 1
+      FROM pg_roles
+      WHERE rolname = current_user
+        AND rolsuper
+    )
+    OR EXISTS (
+      SELECT 1
+      FROM pg_roles
+      WHERE rolname = 'pg_read_all_data'
+        AND pg_has_role(current_user, oid, 'USAGE')
+    )
+    OR EXISTS (
+      SELECT 1
+      FROM aclexplode(COALESCE(c.relacl, acldefault('r', c.relowner))) AS acl
+      WHERE acl.privilege_type = 'SELECT'
+        AND (acl.grantee = 0 OR pg_has_role(acl.grantee, 'USAGE'))
+    )
+  );
 
 -- TODO: support RETURNING and raise a WARNING
 CREATE OR REPLACE RULE geometry_columns_insert AS
diff --git a/regress/core/regress_management.sql b/regress/core/regress_management.sql
index c92b7aad0a..2b5027bd4e 100644
--- a/regress/core/regress_management.sql
+++ b/regress/core/regress_management.sql
@@ -4,5 +4,65 @@ SET client_min_messages TO warning;
 CREATE TABLE test_pt(gid SERIAL PRIMARY KEY, geom geometry);
 INSERT INTO test_pt(geom) VALUES(ST_GeomFromEWKT('SRID=4326;POINT M(1 2 3)'));
 SELECT populate_geometry_columns('test_pt'::regclass);
+SELECT '#6038.before', srid FROM geometry_columns WHERE f_table_schema = 'public' AND f_table_name = 'test_pt' AND f_geometry_column = 'geom';
 SELECT 'The result: ' || DropGeometryTable('test_pt');
+SELECT '#6038.after', count(*) FROM geometry_columns WHERE f_table_schema = 'public' AND f_table_name = 'test_pt' AND f_geometry_column = 'geom';
+DO $$
+DECLARE
+	can_switch_role boolean;
+BEGIN
+	SELECT rolsuper INTO can_switch_role
+	FROM pg_roles
+	WHERE rolname = current_user;
+
+	IF can_switch_role THEN
+		DROP SCHEMA IF EXISTS test6038_private CASCADE;
+		DROP TABLE IF EXISTS public.test6038_visible_geom;
+		DROP ROLE IF EXISTS test6038_invisible;
+		DROP ROLE IF EXISTS test6038_visible;
+		CREATE ROLE test6038_invisible;
+		CREATE ROLE test6038_visible;
+		GRANT test6038_invisible TO CURRENT_USER;
+		GRANT test6038_visible TO CURRENT_USER;
+		CREATE TABLE public.test6038_visible_geom(geom geometry(Point, 4326));
+		GRANT SELECT ON public.test6038_visible_geom TO test6038_visible;
+		CREATE SCHEMA test6038_private;
+		CREATE TABLE test6038_private.hidden_geom(geom geometry(Point, 4326));
+		REVOKE ALL ON SCHEMA test6038_private FROM PUBLIC;
+		GRANT SELECT ON test6038_private.hidden_geom TO test6038_invisible;
+
+		EXECUTE 'SET LOCAL ROLE test6038_visible';
+		IF 1 != (SELECT count(*) FROM geometry_columns WHERE f_table_schema = 'public' AND f_table_name = 'test6038_visible_geom') THEN
+			RAISE EXCEPTION 'geometry_columns did not expose currently selectable table in visible schema';
+		END IF;
+		EXECUTE 'RESET ROLE';
+
+		REVOKE SELECT ON public.test6038_visible_geom FROM test6038_visible;
+		EXECUTE 'SET LOCAL ROLE test6038_visible';
+		IF 0 != (SELECT count(*) FROM geometry_columns WHERE f_table_schema = 'public' AND f_table_name = 'test6038_visible_geom') THEN
+			RAISE EXCEPTION 'geometry_columns exposed metadata after SELECT was revoked in visible schema';
+		END IF;
+		EXECUTE 'RESET ROLE';
+
+		-- The view must not resolve names inside schemas hidden from the caller.
+		EXECUTE 'SET LOCAL ROLE test6038_invisible';
+		IF 1 != (SELECT count(*) FROM geometry_columns WHERE f_table_schema = 'test6038_private') THEN
+			RAISE EXCEPTION 'geometry_columns did not preserve OID-based SELECT visibility in hidden schema';
+		END IF;
+		EXECUTE 'RESET ROLE';
+
+		REVOKE SELECT ON test6038_private.hidden_geom FROM test6038_invisible;
+		EXECUTE 'SET LOCAL ROLE test6038_invisible';
+		IF 0 != (SELECT count(*) FROM geometry_columns WHERE f_table_schema = 'test6038_private') THEN
+			RAISE EXCEPTION 'geometry_columns exposed hidden-schema metadata after SELECT was revoked';
+		END IF;
+		EXECUTE 'RESET ROLE';
+
+		DROP SCHEMA test6038_private CASCADE;
+		DROP TABLE public.test6038_visible_geom;
+		DROP ROLE test6038_invisible;
+		DROP ROLE test6038_visible;
+	END IF;
+END
+$$;
 SELECT 'Unexistant: ' || DropGeometryTable('unexistent'); -- see ticket #861
diff --git a/regress/core/regress_management_expected b/regress/core/regress_management_expected
index e27f7f0ef5..ac137e244f 100644
--- a/regress/core/regress_management_expected
+++ b/regress/core/regress_management_expected
@@ -1,3 +1,5 @@
 1
+#6038.before|4326
 The result: public.test_pt dropped.
+#6038.after|0
 Unexistant: public.unexistent dropped.

commit 8d7a8fe3aba05862f04e21cdc776a63811164f8c
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Sun Aug 9 22:23:44 2026 +0400

    NEWS: reference backported issue numbers

diff --git a/NEWS b/NEWS
index 23f65f6cf3..1a6b38ec50 100644
--- a/NEWS
+++ b/NEWS
@@ -3,11 +3,13 @@ PostGIS 3.3.11
 
 * Bug Fixes and Enhancements *
 
+ - GT-669, Reject truncated size-prefixed header and feature buffers
+   before decoding (Darafei Praliaskouski)
  - Stop the extension upgrade script running ANALYZE inside its transaction,
    where it can deadlock with autovacuum (Darafei Praliaskouski)
  - OSSFuzz 5877056525893632, reject truncated encoded polyline input
    (Darafei Praliaskouski)
- - Fix temporary geometry leaks in ST_DWithin (Darafei Praliaskouski)
+ - GT-677, Fix temporary geometry leaks in ST_DWithin (Darafei Praliaskouski)
  - GT-569, Initialize GSERIALIZED peek test fixtures so check-unit stays
    clean under Valgrind (Darafei Praliaskouski)
  - GT-575, Scope interrupt regression timeouts to the statements being

commit aec36b90d5e924b875c3aea34a1e38f44be36946
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Sun Aug 9 22:19:36 2026 +0400

    flatgeobuf: validate size-prefixed decode buffers
    
    Ported-from: https://gitea.osgeo.org/postgis/postgis/commit/0a7c72a9102033c8b6ddb4f3875cc0741d3baa0a
    (cherry picked from commit dc566d3b328b545533f67f325f23dc49c6c3c280)

diff --git a/deps/flatgeobuf/flatgeobuf_c.cpp b/deps/flatgeobuf/flatgeobuf_c.cpp
index ad5c40c76f..ce81d0b0f3 100644
--- a/deps/flatgeobuf/flatgeobuf_c.cpp
+++ b/deps/flatgeobuf/flatgeobuf_c.cpp
@@ -41,6 +41,19 @@ struct FeatureItem : FlatGeobuf::Item {
 	uint64_t offset;
 };
 
+static bool
+flatgeobuf_size_prefixed_buffer_size(const ctx *ctx, uoffset_t *size)
+{
+	uint64_t remaining;
+	if (ctx->offset > ctx->size)
+		return false;
+	remaining = ctx->size - ctx->offset;
+	if (remaining < sizeof(uoffset_t))
+		return false;
+	*size = flatbuffers::GetPrefixedSize(ctx->buf + ctx->offset);
+	return *size <= remaining - sizeof(uoffset_t);
+}
+
 int flatgeobuf_encode_header(ctx *ctx)
 {
 	FlatBufferBuilder fbb;
@@ -230,11 +243,12 @@ void flatgeobuf_create_index(ctx *ctx)
 int flatgeobuf_decode_feature(ctx *ctx)
 {
 	LWDEBUGF(2, "reading size prefix at %ld", ctx->offset);
-	auto size = flatbuffers::GetPrefixedSize(ctx->buf + ctx->offset);
+	uoffset_t size;
+	if (!flatgeobuf_size_prefixed_buffer_size(ctx, &size)) return -1;
 	LWDEBUGF(2, "size is %ld (without size prefix)", size);
 
-	Verifier verifier(ctx->buf + ctx->offset, size);
-	if (VerifySizePrefixedFeatureBuffer(verifier)) {
+	Verifier verifier(ctx->buf + ctx->offset, size + sizeof(uoffset_t));
+	if (!VerifySizePrefixedFeatureBuffer(verifier)) {
 		lwerror("buffer did not pass verification");
 		return -1;
 	}
@@ -268,11 +282,12 @@ int flatgeobuf_decode_feature(ctx *ctx)
 int flatgeobuf_decode_header(ctx *ctx)
 {
 	LWDEBUGF(2, "reading size prefix at %ld", ctx->offset);
-	auto size = flatbuffers::GetPrefixedSize(ctx->buf + ctx->offset);
+	uoffset_t size;
+	if (!flatgeobuf_size_prefixed_buffer_size(ctx, &size)) return -1;
 	LWDEBUGF(2, "size is %ld (without size prefix)", size);
 
-	Verifier verifier(ctx->buf + ctx->offset, size);
-	if (VerifySizePrefixedHeaderBuffer(verifier)) {
+	Verifier verifier(ctx->buf + ctx->offset, size + sizeof(uoffset_t));
+	if (!VerifySizePrefixedHeaderBuffer(verifier)) {
 		lwerror("buffer did not pass verification");
 		return -1;
 	}
@@ -317,4 +332,4 @@ int flatgeobuf_decode_header(ctx *ctx)
 	}
 
 	return 0;
-}
\ No newline at end of file
+}

commit 67d7524081e5b0bc06a21ba395f8d3550aa7ca5d
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Sun Aug 2 20:03:01 2026 +0400

    Do not ANALYZE inside the extension upgrade transaction
    
    spatial_ref_sys.sql ends with
    
      ON CONFLICT (srid) DO NOTHING;
      COMMIT;
      ANALYZE "spatial_ref_sys";
    
    The ANALYZE sits after COMMIT on purpose: run standalone it refreshes the
    statistics of a table that just gained thousands of rows, outside any
    transaction.  The extension build strips BEGIN and COMMIT because those are
    not allowed in extension scripts, and left the ANALYZE behind — so it ends up
    *inside* the upgrade's transaction, takes ShareUpdateExclusiveLock on
    spatial_ref_sys, and can deadlock against autovacuum analysing the same table:
    
      ERROR:  deadlock detected
      DETAIL:  Process A waits for ShareUpdateExclusiveLock on relation ...;
               blocked by process B.  Process B waits for ShareLock on
               transaction ...; blocked by process A.
      CONTEXT:  SQL statement "ANALYZE "spatial_ref_sys""
                extension script file "postgis--ANY--3.7.0dev.sql"
    
    That aborts ALTER EXTENSION postgis UPDATE, so a user upgrading a busy
    database can simply lose the upgrade.  It also accounts for three of the
    eleven most recent Woodpecker pipeline failures, in regress/test-install and
    regress/test-upgrades, on ordinary amd64 rows rather than emulated ones.
    
    Strip the ANALYZE along with the transaction control it was written to follow.
    The standalone spatial_ref_sys.sql keeps it; autovacuum analyses the table on
    its own schedule, so the extension script does not need to.
    
    (cherry picked from commit 248b014b9805911792e2fcc097ce7c0cde422308)

diff --git a/NEWS b/NEWS
index dc120c19da..23f65f6cf3 100644
--- a/NEWS
+++ b/NEWS
@@ -3,6 +3,8 @@ PostGIS 3.3.11
 
 * Bug Fixes and Enhancements *
 
+ - Stop the extension upgrade script running ANALYZE inside its transaction,
+   where it can deadlock with autovacuum (Darafei Praliaskouski)
  - OSSFuzz 5877056525893632, reject truncated encoded polyline input
    (Darafei Praliaskouski)
  - Fix temporary geometry leaks in ST_DWithin (Darafei Praliaskouski)
diff --git a/extensions/postgis/Makefile.in b/extensions/postgis/Makefile.in
index bc56070103..ddab0176a7 100644
--- a/extensions/postgis/Makefile.in
+++ b/extensions/postgis/Makefile.in
@@ -116,9 +116,21 @@ sql/$(EXTENSION)--unpackaged.sql: Makefile | sql
 sql:
 	mkdir -p $@
 
-#strip BEGIN/COMMIT since these are not allowed in extensions
+# Strip BEGIN/COMMIT since these are not allowed in extensions, and the trailing
+# ANALYZE with them.  In spatial_ref_sys.sql that ANALYZE deliberately sits *after*
+# COMMIT, so run standalone it refreshes statistics outside any transaction.  Removing
+# only the COMMIT moves it inside the extension's transaction, where it takes
+# ShareUpdateExclusiveLock on spatial_ref_sys and can deadlock against autovacuum
+# doing the same work:
+#
+#   ERROR:  deadlock detected
+#   CONTEXT:  SQL statement "ANALYZE "spatial_ref_sys""
+#             extension script file "postgis--ANY--<version>.sql"
+#
+# That aborts ALTER EXTENSION postgis UPDATE on any busy database.  Autovacuum will
+# analyse the table on its own schedule, so the extension script does not need to.
 sql/spatial_ref_sys.sql: ../../spatial_ref_sys.sql | sql
-	$(PERL) -pe 's/BEGIN\;//g ; s/COMMIT\;//g' $< > $@
+	$(PERL) -pe 's/BEGIN\;//g ; s/COMMIT\;//g ; s/^ANALYZE "spatial_ref_sys";\s*$$//g' $< > $@
 
 ../../doc/postgis_comments.sql:
 	$(MAKE) -C ../../doc comments

commit 0f5c7300cd7d53ec67910d0af69bab3e7860ac97
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Sat Aug 8 12:22:14 2026 +0400

    fix(liblwgeom): reject truncated encoded polylines
    
    (cherry picked from commit 491813e413afff546779b988e79ad7eb20559c46)

diff --git a/NEWS b/NEWS
index d2bd4ca484..dc120c19da 100644
--- a/NEWS
+++ b/NEWS
@@ -3,6 +3,8 @@ PostGIS 3.3.11
 
 * Bug Fixes and Enhancements *
 
+ - OSSFuzz 5877056525893632, reject truncated encoded polyline input
+   (Darafei Praliaskouski)
  - Fix temporary geometry leaks in ST_DWithin (Darafei Praliaskouski)
  - GT-569, Initialize GSERIALIZED peek test fixtures so check-unit stays
    clean under Valgrind (Darafei Praliaskouski)
diff --git a/liblwgeom/cunit/cu_in_encoded_polyline.c b/liblwgeom/cunit/cu_in_encoded_polyline.c
index 597493c810..820bd2942d 100644
--- a/liblwgeom/cunit/cu_in_encoded_polyline.c
+++ b/liblwgeom/cunit/cu_in_encoded_polyline.c
@@ -53,6 +53,13 @@ static void in_encoded_polyline_test_precision(void)
 	    "SRID=4326;LINESTRING(-0.250691 49.283048,-0.250633 49.283376,-0.250502 49.283972,-0.251245 49.284028,-0.251938 49.284232,-0.251938 49.2842)");
 }
 
+static void in_encoded_polyline_test_truncated_input(void)
+{
+	/* A latitude without a longitude and an unterminated latitude varint. */
+	CU_ASSERT_PTR_NULL(lwgeom_from_encoded_polyline("A", 5));
+	CU_ASSERT_PTR_NULL(lwgeom_from_encoded_polyline("`", 5));
+}
+
 /*
 ** Used by test harness to register the tests in this file.
 */
@@ -62,4 +69,5 @@ void in_encoded_polyline_suite_setup(void)
 	CU_pSuite suite = CU_add_suite("encoded_polyline_input", NULL, NULL);
 	PG_ADD_TEST(suite, in_encoded_polyline_test_geoms);
 	PG_ADD_TEST(suite, in_encoded_polyline_test_precision);
+	PG_ADD_TEST(suite, in_encoded_polyline_test_truncated_input);
 }
diff --git a/liblwgeom/lwin_encoded_polyline.c b/liblwgeom/lwin_encoded_polyline.c
index 7212314a6a..2920e55054 100644
--- a/liblwgeom/lwin_encoded_polyline.c
+++ b/liblwgeom/lwin_encoded_polyline.c
@@ -28,6 +28,7 @@
 #include <math.h>
 
 #include "liblwgeom.h"
+#include "lwgeom_log.h"
 #include "../postgis_config.h"
 
 LWGEOM*
@@ -51,6 +52,11 @@ lwgeom_from_encoded_polyline(const char *encodedpolyline, int precision)
     int res = 0;
     char shift = 0;
     do {
+      if (idx >= length) {
+        lwerror("lwgeom_from_encoded_polyline: input is truncated");
+        ptarray_free(pa);
+        return NULL;
+      }
       byte = encodedpolyline[idx++] - 63;
       res |= (byte & 0x1F) << shift;
       shift += 5;
@@ -61,6 +67,11 @@ lwgeom_from_encoded_polyline(const char *encodedpolyline, int precision)
     shift = 0;
     res = 0;
     do {
+      if (idx >= length) {
+        lwerror("lwgeom_from_encoded_polyline: input is truncated");
+        ptarray_free(pa);
+        return NULL;
+      }
       byte = encodedpolyline[idx++] - 63;
       res |= (byte & 0x1F) << shift;
       shift += 5;

commit bd670a94f89f730cd66802d9b919c78cedf160b0
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Sun Aug 9 22:02:03 2026 +0400

    postgis: free ST_DWithin temporary geometries
    
    Ported-from: https://gitea.osgeo.org/postgis/postgis/commit/94cc593de819c917edc0d0a49c9c2ee76a5f88ce

diff --git a/NEWS b/NEWS
index 75203f7978..d2bd4ca484 100644
--- a/NEWS
+++ b/NEWS
@@ -3,6 +3,7 @@ PostGIS 3.3.11
 
 * Bug Fixes and Enhancements *
 
+ - Fix temporary geometry leaks in ST_DWithin (Darafei Praliaskouski)
  - GT-569, Initialize GSERIALIZED peek test fixtures so check-unit stays
    clean under Valgrind (Darafei Praliaskouski)
  - GT-575, Scope interrupt regression timeouts to the statements being
diff --git a/postgis/lwgeom_functions_basic.c b/postgis/lwgeom_functions_basic.c
index abe8704d21..d3ad66e53b 100644
--- a/postgis/lwgeom_functions_basic.c
+++ b/postgis/lwgeom_functions_basic.c
@@ -749,6 +749,8 @@ Datum LWGEOM_dwithin(PG_FUNCTION_ARGS)
 	}
 
 	mindist = lwgeom_mindistance2d_tolerance(lwgeom1, lwgeom2, tolerance);
+	lwgeom_free(lwgeom1);
+	lwgeom_free(lwgeom2);
 
 	PG_FREE_IF_COPY(geom1, 0);
 	PG_FREE_IF_COPY(geom2, 1);

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

Summary of changes:
 NEWS                                     | 13 +++++
 deps/flatgeobuf/flatgeobuf_c.cpp         | 29 ++++++++---
 extensions/postgis/Makefile.in           | 16 +++++-
 liblwgeom/cunit/cu_in_encoded_polyline.c |  8 +++
 liblwgeom/lwgeom_geos.c                  | 87 ++++++++++++++++++++++++++++++++
 liblwgeom/lwin_encoded_polyline.c        | 11 ++++
 postgis/lwgeom_box.c                     | 16 ++++--
 postgis/lwgeom_functions_basic.c         |  2 +
 postgis/postgis.sql.in                   | 35 ++++++++++++-
 regress/core/box2d.sql                   |  8 +++
 regress/core/box2d_expected              |  6 +++
 regress/core/normalize.sql               |  8 +++
 regress/core/normalize_expected          |  4 ++
 regress/core/regress_management.sql      | 60 ++++++++++++++++++++++
 regress/core/regress_management_expected |  2 +
 regress/core/tests.mk.in                 |  1 +
 16 files changed, 292 insertions(+), 14 deletions(-)
 create mode 100644 regress/core/box2d.sql
 create mode 100644 regress/core/box2d_expected


hooks/post-receive
-- 
PostGIS


More information about the postgis-tickets mailing list