[SCM] PostGIS branch stable-3.2 updated. 3.2.10-104-g27960c7d1d
git at osgeo.org
git at osgeo.org
Sun Aug 9 12:19:15 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.2 has been updated
via 27960c7d1d9bfdc92398bde485e76ca9069c1f31 (commit)
via a7a53319ec25ab880a198f49de562bcfea51e6d2 (commit)
via 74052cd4453d2373fa5bb3088772c1a2018033ea (commit)
via 11db3be940571779487e40adb201a52d4a3169dc (commit)
via 554b59c0a95b7ccf7121f80fe6e3fa41d7cbd800 (commit)
via 443fbfa442cc994d6d556ff6fea7284c55d56ade (commit)
via 346558f8bbf59400044f5b56314edc7314ffce49 (commit)
via 618c525bf3830be843c6cbb09c4a7e931e2b264d (commit)
via af1df3b0c09c9c72f6796e8277d5d149517fe966 (commit)
from 2031dfeff00afb8454a9057f01bc097e5233c535 (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 27960c7d1d9bfdc92398bde485e76ca9069c1f31
Merge: 2031dfeff0 a7a53319ec
Author: Darafei Praliaskouski <komzpa at gmail.com>
Date: Sun Aug 9 12:19:13 2026 -0700
Merge pull request 'Backport correctness and input-safety fixes to stable-3.2' (!688) from Komzpa/postgis:fix/stable-3.2-backpatch-sweep-20260810 into stable-3.2
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/688
commit a7a53319ec25ab880a198f49de562bcfea51e6d2
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 b9df82cb7e..cf6a2cef9b 100644
--- a/NEWS
+++ b/NEWS
@@ -3,6 +3,8 @@ PostGIS 3.2.11
* 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 77c40ec816..0563ee6fa0 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 74052cd4453d2373fa5bb3088772c1a2018033ea
Author: Darafei Praliaskouski <me at komzpa.net>
Date: Sun Aug 9 22:56:19 2026 +0400
NEWS: reference BOX2D out-of-bounds fix
diff --git a/NEWS b/NEWS
index 0f22c4bee5..b9df82cb7e 100644
--- a/NEWS
+++ b/NEWS
@@ -5,6 +5,8 @@ PostGIS 3.2.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 11db3be940571779487e40adb201a52d4a3169dc
Author: Darafei Praliaskouski <me at komzpa.net>
Date: Sun Aug 9 22:52:17 2026 +0400
Fix out-of-bounds read in BOX2D_out and BOX2D_expand
Ported manually for the stable test-list macro spelling.\n\n(cherry picked from commit 31aec6787f974c94ff5fa0180969d73dd1817e14)
diff --git a/postgis/lwgeom_box.c b/postgis/lwgeom_box.c
index 98b39ed955..5eda3680ef 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)
{
@@ -582,4 +590,3 @@ Datum BOX2D_construct(PG_FUNCTION_ARGS)
PG_RETURN_POINTER(result);
}
-
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 2bda32aeff..b2730b2c27 100644
--- a/regress/core/tests.mk.in
+++ b/regress/core/tests.mk.in
@@ -31,6 +31,7 @@ TESTS += \
$(topsrcdir)/regress/core/bestsrid \
$(topsrcdir)/regress/core/binary \
$(topsrcdir)/regress/core/boundary \
+ $(topsrcdir)/regress/core/box2d \
$(topsrcdir)/regress/core/chaikin \
$(topsrcdir)/regress/core/filterm \
$(topsrcdir)/regress/core/cluster \
commit 554b59c0a95b7ccf7121f80fe6e3fa41d7cbd800
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 8bd70b2713..0f22c4bee5 100644
--- a/NEWS
+++ b/NEWS
@@ -3,6 +3,8 @@ PostGIS 3.2.11
* 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)
- Stop the extension upgrade script running ANALYZE inside its transaction,
diff --git a/postgis/postgis.sql.in b/postgis/postgis.sql.in
index f3fe27772c..88e6e1442c 100644
--- a/postgis/postgis.sql.in
+++ b/postgis/postgis.sql.in
@@ -6214,7 +6214,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 443fbfa442cc994d6d556ff6fea7284c55d56ade
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/NEWS b/NEWS
index f663c5a9dc..8bd70b2713 100644
--- a/NEWS
+++ b/NEWS
@@ -3,6 +3,8 @@ PostGIS 3.2.11
* Bug Fixes *
+ - 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
diff --git a/deps/flatgeobuf/flatgeobuf_c.cpp b/deps/flatgeobuf/flatgeobuf_c.cpp
index bf9b8251ea..72f7590a71 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,13 @@ 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 +283,13 @@ 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 +334,4 @@ int flatgeobuf_decode_header(ctx *ctx)
}
return 0;
-}
\ No newline at end of file
+}
commit 346558f8bbf59400044f5b56314edc7314ffce49
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 2d922d3a6a..f663c5a9dc 100644
--- a/NEWS
+++ b/NEWS
@@ -3,6 +3,8 @@ PostGIS 3.2.11
* Bug Fixes *
+ - 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 d983e6f5f0..a0be72e9c5 100644
--- a/extensions/postgis/Makefile.in
+++ b/extensions/postgis/Makefile.in
@@ -102,9 +102,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 618c525bf3830be843c6cbb09c4a7e931e2b264d
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 b9933f72f8..2d922d3a6a 100644
--- a/NEWS
+++ b/NEWS
@@ -3,6 +3,8 @@ PostGIS 3.2.11
* Bug Fixes *
+ - 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 af1df3b0c09c9c72f6796e8277d5d149517fe966
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 06e740541d..b9933f72f8 100644
--- a/NEWS
+++ b/NEWS
@@ -3,6 +3,7 @@ PostGIS 3.2.11
* Bug Fixes *
+ - 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 f61aeb133d..01482d8bd8 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 | 31 +++++++++---
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 | 17 +++++--
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, 294 insertions(+), 15 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