[SCM] PostGIS branch stable-3.4 updated. 3.4.6-121-g6c71296726

git at osgeo.org git at osgeo.org
Sun Aug 9 12:23:02 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.4 has been updated
       via  6c712967262ba948f77a7642478982a96cc62bc7 (commit)
       via  073949f859c291a5af0e0496ea2320e043ce7e95 (commit)
       via  691978015942f36989ca7653ddecf2d72f640846 (commit)
       via  48a64735201167de711dc60d2ea0be84dad08138 (commit)
       via  424ad924490fca437eb7819a6e1abb6f84affa6d (commit)
       via  69607ac67615c5af9f345c670e69301238049bc8 (commit)
       via  dc566d3b328b545533f67f325f23dc49c6c3c280 (commit)
       via  94ecaa87a354c5347df2fe5c53864b87b032b4c8 (commit)
       via  b186998b61228f62f3359ad77565bb6dccdcd55d (commit)
      from  95e296e07caee9db09979d124e5766fd7e186dba (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 6c712967262ba948f77a7642478982a96cc62bc7
Merge: 95e296e07c 073949f859
Author: Darafei Praliaskouski <komzpa at gmail.com>
Date:   Sun Aug 9 12:23:01 2026 -0700

    Merge pull request 'Backport correctness and input-safety fixes to stable-3.4' (!686) from Komzpa/postgis:fix/stable-3.4-backpatch-sweep-20260810 into stable-3.4
    
    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 and frees temporary geometries in the stable `ST_DWithin` implementation.
    
    `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/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 commit retains upstream authorship; `ST_DWithin` is adapted only for its earlier stable source location. The `ANALYZE` upgrade fix is already in `stable-3.4` and is not duplicated here.
    
    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/686


commit 073949f859c291a5af0e0496ea2320e043ce7e95
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 2f139e379a..cba5cfc17f 100644
--- a/NEWS
+++ b/NEWS
@@ -3,6 +3,8 @@ PostGIS 3.4.7
  
 * Bug Fixes *
 
+  - 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 bbaefa02e3..8a0329d9ef 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);
 
@@ -561,6 +562,7 @@ LWGEOM2GEOS(const LWGEOM* lwgeom, uint8_t autofix)
 		case MULTILINETYPE:
 		case MULTIPOLYGONTYPE:
 		case TINTYPE:
+		case POLYHEDRALSURFACETYPE:
 		case COLLECTIONTYPE:
 		{
 			int geostype;
@@ -706,6 +708,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)
 {
@@ -715,6 +800,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 691978015942f36989ca7653ddecf2d72f640846
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Sun Aug 9 22:56:11 2026 +0400

    NEWS: reference BOX2D out-of-bounds fix

diff --git a/NEWS b/NEWS
index 629105c0c1..2f139e379a 100644
--- a/NEWS
+++ b/NEWS
@@ -5,6 +5,8 @@ PostGIS 3.4.7
 
   - #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)
   - OSSFuzz 5877056525893632, reject truncated encoded polyline input

commit 48a64735201167de711dc60d2ea0be84dad08138
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 a3284e0d87..7d832f2e21 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 424ad924490fca437eb7819a6e1abb6f84affa6d
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 c6e88a5963..629105c0c1 100644
--- a/NEWS
+++ b/NEWS
@@ -3,6 +3,8 @@ PostGIS 3.4.7
  
 * Bug Fixes *
 
+  - #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)
   - OSSFuzz 5877056525893632, reject truncated encoded polyline input
diff --git a/postgis/postgis.sql.in b/postgis/postgis.sql.in
index 1424218608..656765d631 100644
--- a/postgis/postgis.sql.in
+++ b/postgis/postgis.sql.in
@@ -6455,7 +6455,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 69607ac67615c5af9f345c670e69301238049bc8
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 08f692cf14..c6e88a5963 100644
--- a/NEWS
+++ b/NEWS
@@ -3,11 +3,11 @@ PostGIS 3.4.7
  
 * Bug Fixes *
 
-  - [flatgeobuf] Reject truncated size-prefixed header and feature buffers
+  - GT-669, Reject truncated size-prefixed header and feature buffers
     before decoding (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)
   - Stop the extension upgrade script running ANALYZE inside its
     transaction, where it could deadlock with autovacuum analysing
     spatial_ref_sys (Darafei Praliaskouski)

commit dc566d3b328b545533f67f325f23dc49c6c3c280
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

diff --git a/NEWS b/NEWS
index fea20a887c..08f692cf14 100644
--- a/NEWS
+++ b/NEWS
@@ -3,6 +3,8 @@ PostGIS 3.4.7
  
 * Bug Fixes *
 
+  - [flatgeobuf] Reject truncated size-prefixed header and feature buffers
+    before decoding (Darafei Praliaskouski)
   - OSSFuzz 5877056525893632, reject truncated encoded polyline input
     (Darafei Praliaskouski)
   - Fix temporary geometry leaks in ST_DWithin (Darafei Praliaskouski)
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 94ecaa87a354c5347df2fe5c53864b87b032b4c8
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 4fbc23436a..fea20a887c 100644
--- a/NEWS
+++ b/NEWS
@@ -3,6 +3,8 @@ PostGIS 3.4.7
  
 * Bug Fixes *
 
+  - OSSFuzz 5877056525893632, reject truncated encoded polyline input
+    (Darafei Praliaskouski)
   - Fix temporary geometry leaks in ST_DWithin (Darafei Praliaskouski)
   - Stop the extension upgrade script running ANALYZE inside its
     transaction, where it could deadlock with autovacuum analysing
diff --git a/liblwgeom/cunit/cu_in_encoded_polyline.c b/liblwgeom/cunit/cu_in_encoded_polyline.c
index d3d44b4083..8034aef1a5 100644
--- a/liblwgeom/cunit/cu_in_encoded_polyline.c
+++ b/liblwgeom/cunit/cu_in_encoded_polyline.c
@@ -62,6 +62,13 @@ static void in_encoded_polyline_test_close_points(void)
 		"SRID=4326;LINESTRING(38.903876 55.336448,38.903875 55.336448)");
 }
 
+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.
 */
@@ -72,4 +79,5 @@ void in_encoded_polyline_suite_setup(void)
 	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_close_points);
+	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 2bb9f73417..d1af103eab 100644
--- a/liblwgeom/lwin_encoded_polyline.c
+++ b/liblwgeom/lwin_encoded_polyline.c
@@ -29,6 +29,7 @@
 #include <stdint.h>
 
 #include "liblwgeom.h"
+#include "lwgeom_log.h"
 #include "../postgis_config.h"
 
 LWGEOM*
@@ -52,6 +53,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;
@@ -62,6 +68,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 b186998b61228f62f3359ad77565bb6dccdcd55d
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 361f4b074e..4fbc23436a 100644
--- a/NEWS
+++ b/NEWS
@@ -3,6 +3,7 @@ PostGIS 3.4.7
  
 * Bug Fixes *
 
+  - Fix temporary geometry leaks in ST_DWithin (Darafei Praliaskouski)
   - Stop the extension upgrade script running ANALYZE inside its
     transaction, where it could deadlock with autovacuum analysing
     spatial_ref_sys (Darafei Praliaskouski)
diff --git a/postgis/lwgeom_functions_basic.c b/postgis/lwgeom_functions_basic.c
index 03a0fc49bc..5d6a4fd581 100644
--- a/postgis/lwgeom_functions_basic.c
+++ b/postgis/lwgeom_functions_basic.c
@@ -752,6 +752,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                                     | 11 ++++
 deps/flatgeobuf/flatgeobuf_c.cpp         | 29 ++++++++---
 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 +
 15 files changed, 276 insertions(+), 12 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