[SCM] PostGIS branch stable-3.6 updated. 3.6.4-94-g36d54ddb55
git at osgeo.org
git at osgeo.org
Mon Jul 27 16:40:23 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.6 has been updated
via 36d54ddb553302694397c2c4dfae20c13eaa326a (commit)
via ea6db98d4d8a55f8148ef8eae499544246066130 (commit)
via 1e1bc9ff75fba6429dc53eefbfd006724b9c67a9 (commit)
via 8655c98b92feebaf8379d8bf35d844307efd3057 (commit)
via f0bed973805f8f4247380c410d782be8770c00ec (commit)
via 3655dd2c38e18f681f1ff12aee01e4300ba8886b (commit)
via 3bc25ec709c620e39cc2598cd26a66aa36acc451 (commit)
from ff2d0162461490e432f286511d1c05270bd32bbe (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 36d54ddb553302694397c2c4dfae20c13eaa326a
Merge: ff2d016246 ea6db98d4d
Author: Darafei Praliaskouski <komzpa at gmail.com>
Date: Mon Jul 27 16:40:21 2026 -0700
Merge pull request 'Backport safety fixes to stable-3.6' (!537) from Komzpa/postgis:backport/20260726-stable-3.6-safety-fixes into stable-3.6
Reviewed-on: https://gitea.osgeo.org/postgis/postgis/pulls/537
commit ea6db98d4d8a55f8148ef8eae499544246066130
Author: Darafei Praliaskouski <me at komzpa.net>
Date: Mon Jul 27 23:18:46 2026 +0400
ci: allow slow stable postgres recovery
diff --git a/.woodpecker/regress.yml b/.woodpecker/regress.yml
index c5264d4877..d722bcc48e 100644
--- a/.woodpecker/regress.yml
+++ b/.woodpecker/regress.yml
@@ -8,8 +8,9 @@ variables:
- export PATH=/usr/lib/postgresql/$${PGVER}/bin:$${PATH}
- export PGPORT=$$(grep ^port /etc/postgresql/$${PGVER}/main/postgresql.conf | awk '{print $$3}')
# The test image can need crash recovery after clone-local startup. Under
- # multi-workflow I/O load, Debian's default 60-second pg_ctl wait is short.
- - printf '%s\n' "pg_ctl_options = '-t 300'" > /etc/postgresql/$${PGVER}/main/pg_ctl.conf
+ # multi-workflow I/O load, even 300 seconds can be short on saturated
+ # agents while PostgreSQL fsyncs interrupted packaged clusters.
+ - printf '%s\n' "pg_ctl_options = '-t 900'" > /etc/postgresql/$${PGVER}/main/pg_ctl.conf
- export POSTGIS_REGRESS_DB_OWNER=postgis_reg_unprivileged_user
- export RUNTESTFLAGS="-v
--after-create-db-script $${CI_WORKSPACE}/regress/hooks/configure-pgextwlist.sql
commit 1e1bc9ff75fba6429dc53eefbfd006724b9c67a9
Author: Darafei Praliaskouski <me at komzpa.net>
Date: Mon Jul 27 14:33:04 2026 +0400
regress: relax stable 3.6 interrupt tolerances
diff --git a/regress/core/interrupt_relate.sql b/regress/core/interrupt_relate.sql
index d80dacf1cf..ab0f223c3c 100644
--- a/regress/core/interrupt_relate.sql
+++ b/regress/core/interrupt_relate.sql
@@ -19,32 +19,32 @@ SELECT 1::int as id, ST_Collect(g) g FROM (
SET statement_timeout TO 100;
select ST_Contains(g,g) from _inputs WHERE id = 1; -- 6+ seconds
-SELECT _timecheck('contains', '220ms');
+SELECT _timecheck('contains', '500ms');
select ST_Covers(g,g) from _inputs WHERE id = 1; -- 6+ seconds
-SELECT _timecheck('covers', '220ms');
+SELECT _timecheck('covers', '500ms');
select ST_CoveredBy(g,g) from _inputs WHERE id = 1; -- 6+ seconds
-SELECT _timecheck('coveredby', '250ms');
+SELECT _timecheck('coveredby', '500ms');
select ST_Crosses(g,g) from _inputs WHERE id = 1; -- 6+ seconds
-SELECT _timecheck('crosses', '220ms');
+SELECT _timecheck('crosses', '500ms');
-- NOTE: we're reversing one of the operands to avoid the
-- short-circuit described in #3226
select ST_Equals(g,st_reverse(g)) from _inputs WHERE id = 1; -- 6+ seconds
-SELECT _timecheck('equals', '220ms');
+SELECT _timecheck('equals', '500ms');
-- NOTE: intersects became very fast, so we segmentize
-- input to make it slower
select ST_Intersects(g,ST_Segmentize(g,1e-4)) from _inputs WHERE id = 1; -- 6+ seconds
-SELECT _timecheck('intersects', '210ms');
+SELECT _timecheck('intersects', '500ms');
select ST_Overlaps(g,g) from _inputs WHERE id = 1; -- 6+ seconds
-SELECT _timecheck('overlaps', '220ms');
+SELECT _timecheck('overlaps', '500ms');
select ST_Relate(g,g) from _inputs WHERE id = 1; -- 6+ seconds
-SELECT _timecheck('relate', '220ms');
+SELECT _timecheck('relate', '500ms');
DROP FUNCTION _timecheck(text, interval);
DROP TABLE _inputs;
commit 8655c98b92feebaf8379d8bf35d844307efd3057
Author: Darafei Praliaskouski <me at komzpa.net>
Date: Sun Jul 26 21:35:41 2026 +0400
Drop master-only NURBS validator from stable GSERIALIZED port
Stable branches do not define NURBSCURVETYPE, so the NURBS-specific validator from the master GSERIALIZED hardening patch is not a branch-capability match. Keep the payload checks for geometry types present on the stable line.
(cherry picked from commit de00ccc19b0fee48f8dbbc8b521510382624c099)
diff --git a/liblwgeom/gserialized2.c b/liblwgeom/gserialized2.c
index 2831d161fd..a7cf6eefe4 100644
--- a/liblwgeom/gserialized2.c
+++ b/liblwgeom/gserialized2.c
@@ -440,37 +440,6 @@ gserialized2_validate_geometry_buffer(uint8_t *data_ptr, uint8_t *data_end, lwfl
break;
}
- case NURBSCURVETYPE: {
- uint32_t nweights, nknots;
- size_t weight_bytes, knot_bytes, point_bytes;
- consumed = 6 * sizeof(uint32_t);
- if (!gserialized2_range_available(data_ptr, data_end, consumed))
- {
- lwerror("%s: GSERIALIZED NURBS header exceeds payload size", __func__);
- return LW_FAILURE;
- }
- nweights =
- gserialized2_read_uint32_checked(data_ptr + 3 * sizeof(uint32_t), data_end, "NURBS weight count");
- nknots =
- gserialized2_read_uint32_checked(data_ptr + 4 * sizeof(uint32_t), data_end, "NURBS knot count");
- if (gserialized2_checked_mul((size_t)nweights, sizeof(double), &weight_bytes) == LW_FAILURE ||
- gserialized2_checked_mul((size_t)nknots, sizeof(double), &knot_bytes) == LW_FAILURE)
- {
- lwerror("%s: GSERIALIZED NURBS vector size overflows", __func__);
- return LW_FAILURE;
- }
- if (gserialized2_pointarray_payload_size(count, lwflags, &point_bytes) == LW_FAILURE)
- return LW_FAILURE;
- if (gserialized2_checked_add(consumed, weight_bytes, &consumed) == LW_FAILURE ||
- gserialized2_checked_add(consumed, knot_bytes, &consumed) == LW_FAILURE ||
- gserialized2_checked_add(consumed, point_bytes, &consumed) == LW_FAILURE)
- {
- lwerror("%s: GSERIALIZED NURBS size overflows", __func__);
- return LW_FAILURE;
- }
- break;
- }
-
default:
lwerror("Unknown geometry type: %d - %s", type, lwtype_name(type));
return LW_FAILURE;
commit f0bed973805f8f4247380c410d782be8770c00ec
Author: Darafei Praliaskouski <me at komzpa.net>
Date: Sun Jul 26 04:15:53 2026 +0400
Guard largest empty circle boundary inputs
(cherry picked from commit 8d143e4b549567d362edd0e80349bd5de29a4c1e)
(cherry picked from commit 5817fe2806e94d98881ea890c53e9b8112e62a18)
diff --git a/NEWS b/NEWS
index fc806488af..65550de78d 100644
--- a/NEWS
+++ b/NEWS
@@ -7,6 +7,8 @@ PostGIS 3.6.5
geometry data (Darafei Praliaskouski)
- GT-523, [raster] Fix invalid reads in raster band initialization and
geotransform property access (Darafei Praliaskouski)
+- GT-519, Reject invalid ST_LargestEmptyCircle boundaries before GEOS can
+ spend unbounded time on them (Darafei Praliaskouski)
- #6046, Prefer GraphicsMagick for documentation image generation
(Darafei Praliaskouski)
- #5989, CurvePolygon distance corner case (Paul Ramsey)
diff --git a/doc/reference_processing.xml b/doc/reference_processing.xml
index 8144058a45..469092d801 100644
--- a/doc/reference_processing.xml
+++ b/doc/reference_processing.xml
@@ -1647,6 +1647,7 @@ LINESTRING Z (-30 -29.7 5,-29 -27 11,-30 -29.7 10,-36 -31 5,-45 -33 1,-46 -32 11
<refsection>
<title>Description</title>
<para>Finds the largest circle which does not overlap a set of point and line obstacles. (Polygonal geometries may be included as obstacles, but only their boundary lines are used.) The center of the circle is constrained to lie inside a polygonal boundary, which by default is the convex hull of the input geometry. The circle center is the point in the interior of the boundary which has the farthest distance from the obstacles. The circle itself is provided by the center point and a nearest point lying on an obstacle determining the circle radius.</para>
+ <para>If a boundary is supplied it must be a valid polygonal geometry with positive area.</para>
<para>The circle center is determined to a given accuracy specified by a distance tolerance, using an iterative algorithm. If the accuracy distance is not specified a reasonable default is used.</para>
<para>Returns a record with fields:</para>
diff --git a/postgis/lwgeom_geos.c b/postgis/lwgeom_geos.c
index 339214f0d3..61559bf8d3 100644
--- a/postgis/lwgeom_geos.c
+++ b/postgis/lwgeom_geos.c
@@ -438,8 +438,18 @@ Datum ST_LargestEmptyCircle(PG_FUNCTION_ARGS)
GEOSGeometry *ginput, *gcircle, *gcenter, *gnearest;
GEOSGeometry *gboundary = NULL;
double width, height, size;
- GBOX gbox;
- LWGEOM *lwg;
+ double boundary_area;
+ GBOX gbox, boundary_gbox;
+ LWGEOM *lwg, *lwboundary;
+
+ if (hasBoundary &&
+ (!gserialized_get_gbox_p(boundary, &boundary_gbox) ||
+ !(boundary_gbox.xmax > boundary_gbox.xmin && boundary_gbox.ymax > boundary_gbox.ymin)))
+ {
+ lwpgerror("Boundary geometry must have positive area");
+ PG_RETURN_NULL();
+ }
+
lwg = lwgeom_from_gserialized(geom);
if (!lwgeom_isfinite(lwg))
{
@@ -460,6 +470,24 @@ Datum ST_LargestEmptyCircle(PG_FUNCTION_ARGS)
tolerance = size / 1000.0;
}
+ if (hasBoundary)
+ {
+ lwboundary = lwgeom_from_gserialized(boundary);
+ if (!lwgeom_isfinite(lwboundary))
+ {
+ lwgeom_free(lwboundary);
+ lwpgerror("Boundary geometry contains invalid coordinates");
+ PG_RETURN_NULL();
+ }
+ boundary_area = lwgeom_area(lwboundary);
+ lwgeom_free(lwboundary);
+ if (!(boundary_area > 0.0))
+ {
+ lwpgerror("Boundary geometry must have positive area");
+ PG_RETURN_NULL();
+ }
+ }
+
initGEOS(lwpgnotice, lwgeom_geos_error);
ginput = POSTGIS2GEOS(geom);
@@ -468,9 +496,24 @@ Datum ST_LargestEmptyCircle(PG_FUNCTION_ARGS)
if (hasBoundary)
{
+ char boundary_valid;
gboundary = POSTGIS2GEOS(boundary);
if (!gboundary)
HANDLE_GEOS_ERROR("Boundary could not be converted to GEOS");
+ boundary_valid = GEOSisValid(gboundary);
+ if (boundary_valid == 2)
+ {
+ GEOSGeom_destroy(ginput);
+ GEOSGeom_destroy(gboundary);
+ HANDLE_GEOS_ERROR("GEOSisValid");
+ }
+ if (!boundary_valid)
+ {
+ GEOSGeom_destroy(ginput);
+ GEOSGeom_destroy(gboundary);
+ lwpgerror("Boundary geometry is invalid");
+ PG_RETURN_NULL();
+ }
}
gcircle = GEOSLargestEmptyCircle(ginput, gboundary, tolerance);
diff --git a/regress/core/geos39.sql b/regress/core/geos39.sql
index 9d5e025538..ab55d198ec 100644
--- a/regress/core/geos39.sql
+++ b/regress/core/geos39.sql
@@ -52,3 +52,9 @@ SELECT 'lec-1', round(radius::numeric,3),
ST_AsText(nearest,3) AS nearest
FROM ST_LargestEmptyCircle(
'MULTIPOINT ((4 3), (7 6), (4 6))');
+
+SELECT 'lec-invalid-boundary', radius
+FROM ST_LargestEmptyCircle(
+ 'POINT(0 0)'::geometry,
+ 20.1,
+ 'POLYGON((1 2,1 2,1 2,1 2,3 2,1 2))'::geometry);
diff --git a/regress/core/geos39_expected b/regress/core/geos39_expected
index cc507a7f4e..ba385d2029 100644
--- a/regress/core/geos39_expected
+++ b/regress/core/geos39_expected
@@ -10,3 +10,4 @@ rp-2|POINT(1 19)
rp-3|POINT(0 20)
ERROR: Geometry contains invalid coordinates
lec-1|2.121|POINT(5.5 4.5)|POINT(4 3)
+ERROR: Boundary geometry must have positive area
commit 3655dd2c38e18f681f1ff12aee01e4300ba8886b
Author: Darafei Praliaskouski <me at komzpa.net>
Date: Sun Jul 26 16:14:42 2026 +0300
Fix raster invalid reads found by Valgrind
(cherry picked from commit 5692dfe21b854899ee738f278651e58e63532652)
(cherry picked from commit 69462bf75ccaa713fcb51478722ccb86235d8264)
diff --git a/NEWS b/NEWS
index e9be731cc4..fc806488af 100644
--- a/NEWS
+++ b/NEWS
@@ -5,6 +5,8 @@ PostGIS 3.6.5
- GT-522, Reject malformed GSERIALIZED payload counts before deserializing
geometry data (Darafei Praliaskouski)
+- GT-523, [raster] Fix invalid reads in raster band initialization and
+ geotransform property access (Darafei Praliaskouski)
- #6046, Prefer GraphicsMagick for documentation image generation
(Darafei Praliaskouski)
- #5989, CurvePolygon distance corner case (Paul Ramsey)
diff --git a/raster/rt_core/rt_band.c b/raster/rt_core/rt_band.c
index 4578609a27..5af9b26b9e 100644
--- a/raster/rt_core/rt_band.c
+++ b/raster/rt_core/rt_band.c
@@ -133,6 +133,8 @@ rt_band_init_value(
memset(mem, 0, memsize);
return;
}
+ if (numval == 0)
+ return;
switch (pixtype) {
case PT_1BB:
diff --git a/raster/rt_pg/rtpg_raster_properties.c b/raster/rt_pg/rtpg_raster_properties.c
index 032e4975e9..c2717585e0 100644
--- a/raster/rt_pg/rtpg_raster_properties.c
+++ b/raster/rt_pg/rtpg_raster_properties.c
@@ -445,10 +445,8 @@ Datum RASTER_getGeotransform(PG_FUNCTION_ARGS)
double jmag;
double theta_i;
double theta_ij;
- /*
double xoffset;
double yoffset;
- */
TupleDesc result_tuple; /* for returning a composite */
Datum values[VALUES_LENGTH];
@@ -478,6 +476,8 @@ Datum RASTER_getGeotransform(PG_FUNCTION_ARGS)
rt_raster_get_y_skew(raster),
rt_raster_get_y_scale(raster),
&imag, &jmag, &theta_i, &theta_ij) ;
+ xoffset = rt_raster_get_x_offset(raster);
+ yoffset = rt_raster_get_y_offset(raster);
rt_raster_destroy(raster);
PG_FREE_IF_COPY(pgraster, 0);
@@ -501,8 +501,8 @@ Datum RASTER_getGeotransform(PG_FUNCTION_ARGS)
values[1] = Float8GetDatum(jmag);
values[2] = Float8GetDatum(theta_i);
values[3] = Float8GetDatum(theta_ij);
- values[4] = Float8GetDatum(rt_raster_get_x_offset(raster));
- values[5] = Float8GetDatum(rt_raster_get_y_offset(raster));
+ values[4] = Float8GetDatum(xoffset);
+ values[5] = Float8GetDatum(yoffset);
memset(nulls, FALSE, sizeof(bool) * VALUES_LENGTH);
commit 3bc25ec709c620e39cc2598cd26a66aa36acc451
Author: Darafei Praliaskouski <me at komzpa.net>
Date: Sun Jul 26 14:43:56 2026 +0400
Harden GSERIALIZED payload readers
(cherry picked from commit 62cff7ebdd8a76e61231b379026a41dd6e49b436)
diff --git a/NEWS b/NEWS
index 2fe473c21a..e9be731cc4 100644
--- a/NEWS
+++ b/NEWS
@@ -3,6 +3,8 @@ PostGIS 3.6.5
* Fixes *
+- GT-522, Reject malformed GSERIALIZED payload counts before deserializing
+ geometry data (Darafei Praliaskouski)
- #6046, Prefer GraphicsMagick for documentation image generation
(Darafei Praliaskouski)
- #5989, CurvePolygon distance corner case (Paul Ramsey)
diff --git a/liblwgeom/cunit/cu_gserialized1.c b/liblwgeom/cunit/cu_gserialized1.c
index 47fb42e02e..fe5c2ca413 100644
--- a/liblwgeom/cunit/cu_gserialized1.c
+++ b/liblwgeom/cunit/cu_gserialized1.c
@@ -1252,6 +1252,47 @@ test_gserialized1_peek_first_point(void)
CU_ASSERT(peek1_point_helper("POLYGON((0 0, 1 1, 1 0, 0 0))", &p) == LW_FAILURE);
}
+static void
+assert_gserialized1_malformed_rejected(GSERIALIZED *g)
+{
+ LWGEOM *geom;
+ GBOX box;
+ POINT4D point;
+
+ memset(&box, 0, sizeof(box));
+ memset(&point, 0, sizeof(point));
+
+ cu_error_msg_reset();
+ geom = lwgeom_from_gserialized1(g);
+ CU_ASSERT_PTR_NULL(geom);
+ CU_ASSERT_NOT_EQUAL(strlen(cu_error_msg), 0);
+
+ CU_ASSERT_TRUE(gserialized1_is_empty(g));
+ CU_ASSERT_EQUAL(gserialized1_peek_gbox_p(g, &box), LW_FAILURE);
+ CU_ASSERT_EQUAL(gserialized1_peek_first_point(g, &point), LW_FAILURE);
+}
+
+static void
+test_gserialized1_malformed_declared_size(void)
+{
+ LWGEOM *lwgeom = lwgeom_from_wkt("POINT(1 1)", LW_PARSER_CHECK_NONE);
+ GSERIALIZED *g;
+ size_t size = 0;
+
+ CU_ASSERT_PTR_NOT_NULL_FATAL(lwgeom);
+ g = gserialized1_from_lwgeom(lwgeom, &size);
+ CU_ASSERT_PTR_NOT_NULL_FATAL(g);
+
+ LWSIZE_SET(g->size, 0);
+ assert_gserialized1_malformed_rejected(g);
+
+ LWSIZE_SET(g->size, offsetof(GSERIALIZED, data) - 1);
+ assert_gserialized1_malformed_rejected(g);
+
+ lwfree(g);
+ lwgeom_free(lwgeom);
+}
+
/*
** Used by test harness to register the tests in this file.
*/
@@ -1286,4 +1327,5 @@ void gserialized1_suite_setup(void)
PG_ADD_TEST(suite, test_gbox_same_2d);
PG_ADD_TEST(suite, test_signum_macro);
PG_ADD_TEST(suite, test_gserialized1_peek_first_point);
+ PG_ADD_TEST(suite, test_gserialized1_malformed_declared_size);
}
diff --git a/liblwgeom/cunit/cu_gserialized2.c b/liblwgeom/cunit/cu_gserialized2.c
index dd32828c75..6ffdc5badc 100644
--- a/liblwgeom/cunit/cu_gserialized2.c
+++ b/liblwgeom/cunit/cu_gserialized2.c
@@ -509,6 +509,133 @@ test_gserialized2_peek_first_point(void)
CU_ASSERT(peek2_point_helper("POLYGON((0 0, 1 1, 1 0, 0 0))", &p) == LW_FAILURE);
}
+static GSERIALIZED *
+gserialized2_from_hexbytes(const char *hex)
+{
+ uint8_t *bytes;
+ size_t size;
+ GSERIALIZED *g;
+
+ size = strlen(hex) / 2;
+ bytes = bytes_from_hexbytes(hex, strlen(hex));
+ g = lwalloc(size);
+ memcpy(g, bytes, size);
+ lwfree(bytes);
+ return g;
+}
+
+static void
+test_gserialized2_malformed_collection_count(void)
+{
+ const char *hex =
+ "8001000000000044000000000000803f0000000000000040"
+ "070000000200000001000000f03f000000000000f03f";
+ GSERIALIZED *g = gserialized2_from_hexbytes(hex);
+ LWGEOM *geom;
+
+ cu_error_msg_reset();
+ geom = lwgeom_from_gserialized2(g);
+
+ CU_ASSERT_PTR_NULL(geom);
+ CU_ASSERT_NOT_EQUAL(strlen(cu_error_msg), 0);
+
+ lwfree(g);
+}
+
+static void
+assert_gserialized2_malformed_rejected(GSERIALIZED *g)
+{
+ LWGEOM *geom;
+ GBOX box;
+ POINT4D point;
+
+ memset(&box, 0, sizeof(box));
+ memset(&point, 0, sizeof(point));
+
+ cu_error_msg_reset();
+ geom = lwgeom_from_gserialized2(g);
+ CU_ASSERT_PTR_NULL(geom);
+ CU_ASSERT_NOT_EQUAL(strlen(cu_error_msg), 0);
+
+ CU_ASSERT_TRUE(gserialized2_is_empty(g));
+ CU_ASSERT_EQUAL(gserialized2_peek_gbox_p(g, &box), LW_FAILURE);
+ CU_ASSERT_EQUAL(gserialized2_peek_first_point(g, &point), LW_FAILURE);
+}
+
+static void
+test_gserialized2_malformed_declared_size(void)
+{
+ const char *declared_size_0 =
+ "0000000000000044000000000000803f0000000000000040"
+ "070000000200000001000000f03f000000000000f03f";
+ const char *declared_size_23 =
+ "5c00000000000044000000000000803f0000000000000040"
+ "070000000200000001000000f03f000000000000f03f";
+ GSERIALIZED *g;
+
+ g = gserialized2_from_hexbytes(declared_size_0);
+ assert_gserialized2_malformed_rejected(g);
+ lwfree(g);
+
+ g = gserialized2_from_hexbytes(declared_size_23);
+ assert_gserialized2_malformed_rejected(g);
+ lwfree(g);
+}
+
+static void
+test_gserialized2_malformed_short_allocation(void)
+{
+#if defined(POSTGIS_ASAN_ALLOCATOR_SIZE)
+ const char *hex = "8001000000000044";
+ uint8_t *bytes = bytes_from_hexbytes(hex, strlen(hex));
+ GSERIALIZED *g = malloc(strlen(hex) / 2);
+
+ CU_ASSERT_PTR_NOT_NULL_FATAL(g);
+ memcpy(g, bytes, strlen(hex) / 2);
+ assert_gserialized2_malformed_rejected(g);
+
+ free(g);
+ lwfree(bytes);
+#else
+ CU_PASS("shorter-than-declared allocation validation requires AddressSanitizer allocation metadata");
+#endif
+}
+
+static void
+test_gserialized2_wkb_roundtrip_float_rounded_box(void)
+{
+ const char *hex =
+ "0102000000030000000000000000000000000000f03f0000000000000040"
+ "0101000000000000000f03f0000000000000040000000000000f03f";
+ size_t wkb_size = strlen(hex) / 2;
+ uint8_t *wkb = bytes_from_hexbytes(hex, strlen(hex));
+ LWGEOM *input = lwgeom_from_wkb(wkb, wkb_size, LW_PARSER_CHECK_NONE);
+ GSERIALIZED *serialized;
+ LWGEOM *roundtrip;
+ size_t serialized_size = 0;
+ GBOX input_box;
+ GBOX roundtrip_box;
+
+ CU_ASSERT_PTR_NOT_NULL_FATAL(input);
+ serialized = gserialized2_from_lwgeom(input, &serialized_size);
+ CU_ASSERT_PTR_NOT_NULL_FATAL(serialized);
+ roundtrip = lwgeom_from_gserialized2(serialized);
+ CU_ASSERT_PTR_NOT_NULL_FATAL(roundtrip);
+
+ CU_ASSERT_EQUAL(serialized_size, LWSIZE_GET(serialized->size));
+ CU_ASSERT_PTR_NOT_NULL(input->bbox);
+ CU_ASSERT_PTR_NOT_NULL(roundtrip->bbox);
+ CU_ASSERT_FALSE(gbox_same(input->bbox, roundtrip->bbox));
+ CU_ASSERT_EQUAL(lwgeom_calculate_gbox_cartesian(input, &input_box), LW_SUCCESS);
+ CU_ASSERT_EQUAL(lwgeom_calculate_gbox_cartesian(roundtrip, &roundtrip_box), LW_SUCCESS);
+ CU_ASSERT_TRUE(gbox_same(&input_box, &roundtrip_box));
+
+ lwgeom_free(input);
+ lwgeom_free(roundtrip);
+ lwfree(serialized);
+ lwfree(wkb);
+}
+
/*
** Used by test harness to register the tests in this file.
*/
@@ -527,4 +654,8 @@ void gserialized2_suite_setup(void)
PG_ADD_TEST(suite, test_gserialized2_peek_gbox_p_fails_for_unsupported_cases);
PG_ADD_TEST(suite, test_gserialized2_extended_flags);
PG_ADD_TEST(suite, test_gserialized2_peek_first_point);
+ PG_ADD_TEST(suite, test_gserialized2_malformed_collection_count);
+ PG_ADD_TEST(suite, test_gserialized2_malformed_declared_size);
+ PG_ADD_TEST(suite, test_gserialized2_malformed_short_allocation);
+ PG_ADD_TEST(suite, test_gserialized2_wkb_roundtrip_float_rounded_box);
}
diff --git a/liblwgeom/gserialized1.c b/liblwgeom/gserialized1.c
index 6a34cf1f6b..4528a790f2 100644
--- a/liblwgeom/gserialized1.c
+++ b/liblwgeom/gserialized1.c
@@ -30,13 +30,37 @@
#include "gserialized1.h"
#include <stddef.h>
+#if defined(__has_feature)
+#if __has_feature(address_sanitizer)
+#define POSTGIS_ASAN_ALLOCATOR_SIZE 1
+#endif
+#endif
+#if defined(__SANITIZE_ADDRESS__)
+#define POSTGIS_ASAN_ALLOCATOR_SIZE 1
+#endif
+#if defined(POSTGIS_ASAN_ALLOCATOR_SIZE)
+#include <sanitizer/allocator_interface.h>
+#endif
/***********************************************************************
* GSERIALIZED metadata utility functions.
*/
static int gserialized1_read_gbox_p(const GSERIALIZED *g, GBOX *gbox);
+static int gserialized1_payload_bounds(const GSERIALIZED *g, uint8_t **start, uint8_t **end);
+static int gserialized1_validate_geometry_buffer(uint8_t *data_ptr, uint8_t *data_end, lwflags_t lwflags, size_t *size);
+static size_t
+gserialized1_buffer_size(const GSERIALIZED *g)
+{
+ size_t gsize = LWSIZE_GET(g->size);
+#if defined(POSTGIS_ASAN_ALLOCATOR_SIZE)
+ size_t allocated_size = __sanitizer_get_allocated_size(g);
+ if (allocated_size > 0 && allocated_size < gsize)
+ return allocated_size;
+#endif
+ return gsize;
+}
lwflags_t gserialized1_get_lwflags(const GSERIALIZED *g)
{
@@ -132,6 +156,13 @@ static uint32_t gserialized1_header_size(const GSERIALIZED *gser)
uint32_t gserialized1_get_type(const GSERIALIZED *g)
{
uint32_t *ptr;
+ size_t hsz = gserialized1_header_size(g);
+ size_t gsize = gserialized1_buffer_size(g);
+ if (gsize < hsz + sizeof(uint32_t))
+ {
+ lwerror("%s: GSERIALIZED too small for geometry type", __func__);
+ return 0;
+ }
ptr = (uint32_t*)(g->data);
if ( G1FLAGS_GET_BBOX(g->gflags) )
{
@@ -142,19 +173,19 @@ uint32_t gserialized1_get_type(const GSERIALIZED *g)
int32_t gserialized1_get_srid(const GSERIALIZED *s)
{
- int32_t srid = 0;
- srid = srid | (s->srid[0] << 16);
- srid = srid | (s->srid[1] << 8);
- srid = srid | s->srid[2];
- /* Only the first 21 bits are set. Slide up and back to pull
- the negative bits down, if we need them. */
- srid = (srid<<11)>>11;
+ uint32_t srid = 0;
+ srid = srid | ((uint32_t)s->srid[0] << 16);
+ srid = srid | ((uint32_t)s->srid[1] << 8);
+ srid = srid | (uint32_t)s->srid[2];
+ /* Only the first 21 bits are set. Sign-extend without signed shift UB. */
+ if (srid & 0x00100000)
+ srid |= 0xFFE00000;
/* 0 is our internal unknown value. We'll map back and forth here for now */
if ( srid == 0 )
return SRID_UNKNOWN;
else
- return srid;
+ return (int32_t)srid;
}
void gserialized1_set_srid(GSERIALIZED *s, int32_t srid)
@@ -173,6 +204,204 @@ void gserialized1_set_srid(GSERIALIZED *s, int32_t srid)
s->srid[2] = (srid & 0x000000FF);
}
+static int
+gserialized1_range_available(const uint8_t *ptr, const uint8_t *end, size_t len)
+{
+ return ptr <= end && len <= (size_t)(end - ptr);
+}
+
+static int
+gserialized1_checked_mul(size_t a, size_t b, size_t *out)
+{
+ if (a != 0 && b > SIZE_MAX / a)
+ return LW_FAILURE;
+ *out = a * b;
+ return LW_SUCCESS;
+}
+
+static int
+gserialized1_checked_add(size_t a, size_t b, size_t *out)
+{
+ if (b > SIZE_MAX - a)
+ return LW_FAILURE;
+ *out = a + b;
+ return LW_SUCCESS;
+}
+
+static int
+gserialized1_payload_bounds(const GSERIALIZED *g, uint8_t **start, uint8_t **end)
+{
+ size_t hsz;
+ size_t gsize;
+
+ if (!g)
+ return LW_FAILURE;
+
+ hsz = gserialized1_header_size(g);
+ gsize = gserialized1_buffer_size(g);
+ if (gsize < hsz)
+ return LW_FAILURE;
+
+ if (start)
+ *start = (uint8_t *)g + hsz;
+ if (end)
+ *end = (uint8_t *)g + gsize;
+ return LW_SUCCESS;
+}
+
+static uint32_t
+gserialized1_read_uint32_checked(uint8_t *ptr, uint8_t *end, const char *field)
+{
+ uint32_t value = 0;
+ if (!gserialized1_range_available(ptr, end, sizeof(uint32_t)))
+ {
+ lwerror("%s: GSERIALIZED too small for %s", __func__, field);
+ return 0;
+ }
+ memcpy(&value, ptr, sizeof(uint32_t));
+ return value;
+}
+
+static int
+gserialized1_pointarray_payload_size(uint32_t npoints, lwflags_t lwflags, size_t *nbytes)
+{
+ if (gserialized1_checked_mul((size_t)npoints, sizeof(double) * FLAGS_NDIMS(lwflags), nbytes) == LW_FAILURE)
+ {
+ lwerror("%s: GSERIALIZED point count overflows payload size", __func__);
+ return LW_FAILURE;
+ }
+ return LW_SUCCESS;
+}
+
+static int
+gserialized1_validate_geometry_buffer(uint8_t *data_ptr, uint8_t *data_end, lwflags_t lwflags, size_t *size)
+{
+ uint32_t type, count;
+ size_t consumed = 0;
+
+ if (!gserialized1_range_available(data_ptr, data_end, 2 * sizeof(uint32_t)))
+ {
+ lwerror("%s: GSERIALIZED geometry payload is too short", __func__);
+ return LW_FAILURE;
+ }
+
+ type = gserialized1_read_uint32_checked(data_ptr, data_end, "geometry type");
+ count = gserialized1_read_uint32_checked(data_ptr + sizeof(uint32_t), data_end, "geometry count");
+
+ switch (type)
+ {
+ case POINTTYPE:
+ if (count > 1)
+ {
+ lwerror("%s: invalid point count %u", __func__, count);
+ return LW_FAILURE;
+ }
+ /* fall through */
+ case LINETYPE:
+ case CIRCSTRINGTYPE:
+ case TRIANGLETYPE: {
+ size_t point_bytes;
+ if (gserialized1_pointarray_payload_size(count, lwflags, &point_bytes) == LW_FAILURE)
+ return LW_FAILURE;
+ consumed = 2 * sizeof(uint32_t) + point_bytes;
+ break;
+ }
+
+ case POLYGONTYPE: {
+ size_t ring_counts_size, ring_counts_padded_size, point_bytes_sum = 0;
+ uint8_t *ring_counts = data_ptr + 2 * sizeof(uint32_t);
+ uint32_t i;
+
+ if (gserialized1_checked_mul((size_t)count, sizeof(uint32_t), &ring_counts_size) == LW_FAILURE)
+ {
+ lwerror("%s: GSERIALIZED ring count overflows payload size", __func__);
+ return LW_FAILURE;
+ }
+ ring_counts_padded_size = ring_counts_size + ((count % 2) ? sizeof(uint32_t) : 0);
+ if (!gserialized1_range_available(ring_counts, data_end, ring_counts_padded_size))
+ {
+ lwerror("%s: GSERIALIZED polygon ring table exceeds payload size", __func__);
+ return LW_FAILURE;
+ }
+
+ for (i = 0; i < count; i++)
+ {
+ size_t ring_point_bytes;
+ uint32_t npoints = gserialized1_read_uint32_checked(
+ ring_counts + i * sizeof(uint32_t), data_end, "ring point count");
+ if (gserialized1_pointarray_payload_size(npoints, lwflags, &ring_point_bytes) == LW_FAILURE)
+ return LW_FAILURE;
+ if (gserialized1_checked_add(point_bytes_sum, ring_point_bytes, &point_bytes_sum) == LW_FAILURE)
+ {
+ lwerror("%s: GSERIALIZED polygon coordinate size overflows", __func__);
+ return LW_FAILURE;
+ }
+ }
+ consumed = 2 * sizeof(uint32_t) + ring_counts_padded_size;
+ if (gserialized1_checked_add(consumed, point_bytes_sum, &consumed) == LW_FAILURE)
+ {
+ lwerror("%s: GSERIALIZED polygon size overflows", __func__);
+ return LW_FAILURE;
+ }
+ break;
+ }
+
+ case MULTIPOINTTYPE:
+ case MULTILINETYPE:
+ case MULTIPOLYGONTYPE:
+ case COMPOUNDTYPE:
+ case CURVEPOLYTYPE:
+ case MULTICURVETYPE:
+ case MULTISURFACETYPE:
+ case POLYHEDRALSURFACETYPE:
+ case TINTYPE:
+ case COLLECTIONTYPE: {
+ uint32_t i;
+ uint8_t *subgeom_ptr = data_ptr + 2 * sizeof(uint32_t);
+ lwflags_t subflags = lwflags;
+ FLAGS_SET_BBOX(subflags, 0);
+ for (i = 0; i < count; i++)
+ {
+ size_t subsize = 0;
+ uint32_t subtype =
+ gserialized1_read_uint32_checked(subgeom_ptr, data_end, "collection subtype");
+ if (!lwcollection_allows_subtype(type, subtype))
+ {
+ lwerror("Invalid subtype (%s) for collection type (%s)",
+ lwtype_name(subtype),
+ lwtype_name(type));
+ return LW_FAILURE;
+ }
+ if (gserialized1_validate_geometry_buffer(subgeom_ptr, data_end, subflags, &subsize) ==
+ LW_FAILURE)
+ return LW_FAILURE;
+ if (subsize == 0)
+ {
+ lwerror("%s: GSERIALIZED collection member has zero size", __func__);
+ return LW_FAILURE;
+ }
+ subgeom_ptr += subsize;
+ }
+ consumed = (size_t)(subgeom_ptr - data_ptr);
+ break;
+ }
+
+ default:
+ lwerror("Unknown geometry type: %d - %s", type, lwtype_name(type));
+ return LW_FAILURE;
+ }
+
+ if (!gserialized1_range_available(data_ptr, data_end, consumed))
+ {
+ lwerror("%s: GSERIALIZED geometry payload exceeds declared size", __func__);
+ return LW_FAILURE;
+ }
+
+ if (size)
+ *size = consumed;
+ return LW_SUCCESS;
+}
+
static size_t gserialized1_is_empty_recurse(const uint8_t *p, int *isempty);
static size_t gserialized1_is_empty_recurse(const uint8_t *p, int *isempty)
{
@@ -203,13 +432,17 @@ static size_t gserialized1_is_empty_recurse(const uint8_t *p, int *isempty)
int gserialized1_is_empty(const GSERIALIZED *g)
{
- uint8_t *p = (uint8_t*)g;
+ uint8_t *p, *end;
int isempty = 0;
assert(g);
- p += 8; /* Skip varhdr and srid/flags */
- if(gserialized1_has_bbox(g))
- p += gserialized1_box_size(g); /* Skip the box */
+ if (gserialized1_payload_bounds(g, &p, &end) == LW_FAILURE)
+ {
+ lwerror("%s: invalid GSERIALIZED header size", __func__);
+ return LW_TRUE;
+ }
+ if (gserialized1_validate_geometry_buffer(p, end, gserialized1_get_lwflags(g), NULL) == LW_FAILURE)
+ return LW_TRUE;
gserialized1_is_empty_recurse(p, &isempty);
return isempty;
@@ -254,6 +487,8 @@ int gserialized1_read_gbox_p(const GSERIALIZED *g, GBOX *gbox)
/* Null input! */
if ( ! ( g && gbox ) ) return LW_FAILURE;
+ if (gserialized1_buffer_size(g) < offsetof(GSERIALIZED, data) + gserialized1_box_size(g))
+ return LW_FAILURE;
/* Initialize the flags on the box */
gbox->flags = gserialized1_get_lwflags(g);
@@ -298,7 +533,16 @@ int gserialized1_read_gbox_p(const GSERIALIZED *g, GBOX *gbox)
int
gserialized1_peek_gbox_p(const GSERIALIZED *g, GBOX *gbox)
{
- uint32_t type = gserialized1_get_type(g);
+ uint32_t type;
+ uint8_t *geometry_start = NULL;
+ uint8_t *geometry_end = NULL;
+
+ if (gserialized1_payload_bounds(g, &geometry_start, &geometry_end) == LW_FAILURE)
+ return LW_FAILURE;
+ if (gserialized1_validate_geometry_buffer(geometry_start, geometry_end, gserialized1_get_lwflags(g), NULL) ==
+ LW_FAILURE)
+ return LW_FAILURE;
+ type = gserialized1_get_type(g);
/* Peeking doesn't help if you already have a box or are geodetic */
if ( G1FLAGS_GET_GEODETIC(g->gflags) || G1FLAGS_GET_BBOX(g->gflags) )
@@ -491,11 +735,14 @@ gserialized1_copy_point(double *dptr, lwflags_t flags, POINT4D *out_point)
int
gserialized1_peek_first_point(const GSERIALIZED *g, POINT4D *out_point)
{
- uint8_t *geometry_start = ((uint8_t *)g->data);
- if (gserialized1_has_bbox(g))
- {
- geometry_start += gserialized1_box_size(g);
- }
+ uint8_t *geometry_start = NULL;
+ uint8_t *geometry_end = NULL;
+
+ if (gserialized1_payload_bounds(g, &geometry_start, &geometry_end) == LW_FAILURE)
+ return LW_FAILURE;
+ if (gserialized1_validate_geometry_buffer(geometry_start, geometry_end, gserialized1_get_lwflags(g), NULL) ==
+ LW_FAILURE)
+ return LW_FAILURE;
uint32_t isEmpty = (((uint32_t *)geometry_start)[1]) == 0;
if (isEmpty)
@@ -1454,14 +1701,19 @@ LWGEOM* lwgeom_from_gserialized1(const GSERIALIZED *g)
assert(g);
srid = gserialized1_get_srid(g);
- lwtype = gserialized1_get_type(g);
lwflags = gserialized1_get_lwflags(g);
- LWDEBUGF(4, "Got type %d (%s), srid=%d", lwtype, lwtype_name(lwtype), srid);
+ if (gserialized1_payload_bounds(g, &data_ptr, NULL) == LW_FAILURE)
+ {
+ lwerror("%s: invalid GSERIALIZED header size", __func__);
+ return NULL;
+ }
+ if (gserialized1_validate_geometry_buffer(
+ data_ptr, (uint8_t *)g + gserialized1_buffer_size(g), lwflags, NULL) == LW_FAILURE)
+ return NULL;
+ lwtype = gserialized1_get_type(g);
- data_ptr = (uint8_t*)g->data;
- if (FLAGS_GET_BBOX(lwflags))
- data_ptr += gbox_serialized_size(lwflags);
+ LWDEBUGF(4, "Got type %d (%s), srid=%d", lwtype, lwtype_name(lwtype), srid);
lwgeom = lwgeom_from_gserialized1_buffer(data_ptr, lwflags, &size);
@@ -1495,6 +1747,8 @@ const float * gserialized1_get_float_box_p(const GSERIALIZED *g, size_t *ndims)
*ndims = G1FLAGS_NDIMS_BOX(g->gflags);
if (!g) return NULL;
if (!G1FLAGS_GET_BBOX(g->gflags)) return NULL;
+ if (gserialized1_buffer_size(g) < offsetof(GSERIALIZED, data) + gserialized1_box_size(g))
+ return NULL;
return (const float *)(g->data);
}
@@ -1604,4 +1858,3 @@ GSERIALIZED* gserialized1_drop_gbox(GSERIALIZED *g)
return g_out;
}
-
diff --git a/liblwgeom/gserialized2.c b/liblwgeom/gserialized2.c
index f2d824b171..2831d161fd 100644
--- a/liblwgeom/gserialized2.c
+++ b/liblwgeom/gserialized2.c
@@ -48,18 +48,43 @@
#include "gserialized2.h"
#include <stddef.h>
+#if defined(__has_feature)
+#if __has_feature(address_sanitizer)
+#define POSTGIS_ASAN_ALLOCATOR_SIZE 1
+#endif
+#endif
+#if defined(__SANITIZE_ADDRESS__)
+#define POSTGIS_ASAN_ALLOCATOR_SIZE 1
+#endif
+#if defined(POSTGIS_ASAN_ALLOCATOR_SIZE)
+#include <sanitizer/allocator_interface.h>
+#endif
/***********************************************************************
* GSERIALIZED metadata utility functions.
*/
static int gserialized2_read_gbox_p(const GSERIALIZED *g, GBOX *gbox);
+static int gserialized2_payload_bounds(const GSERIALIZED *g, uint8_t **start, uint8_t **end);
+static int gserialized2_validate_geometry_buffer(uint8_t *data_ptr, uint8_t *data_end, lwflags_t lwflags, size_t *size);
+static size_t
+gserialized2_buffer_size(const GSERIALIZED *g)
+{
+ size_t gsize = LWSIZE_GET(g->size);
+#if defined(POSTGIS_ASAN_ALLOCATOR_SIZE)
+ size_t allocated_size = __sanitizer_get_allocated_size(g);
+ if (allocated_size > 0 && allocated_size < gsize)
+ return allocated_size;
+#endif
+ return gsize;
+}
lwflags_t gserialized2_get_lwflags(const GSERIALIZED *g)
{
lwflags_t lwflags = 0;
uint8_t gflags = g->gflags;
+ size_t gsize = gserialized2_buffer_size(g);
FLAGS_SET_Z(lwflags, G2FLAGS_GET_Z(gflags));
FLAGS_SET_M(lwflags, G2FLAGS_GET_M(gflags));
FLAGS_SET_BBOX(lwflags, G2FLAGS_GET_BBOX(gflags));
@@ -67,6 +92,11 @@ lwflags_t gserialized2_get_lwflags(const GSERIALIZED *g)
if (G2FLAGS_GET_EXTENDED(gflags))
{
uint64_t xflags = 0;
+ if (gsize < offsetof(GSERIALIZED, data) + sizeof(uint64_t))
+ {
+ lwerror("%s: GSERIALIZED too small for extended flags", __func__);
+ return lwflags;
+ }
memcpy(&xflags, g->data, sizeof(uint64_t));
FLAGS_SET_SOLID(lwflags, xflags & G2FLAG_X_SOLID);
}
@@ -185,24 +215,31 @@ uint32_t gserialized2_max_header_size(void)
uint32_t gserialized2_get_type(const GSERIALIZED *g)
{
uint8_t *ptr = gserialized2_get_geometry_p(g);
+ size_t hsz = gserialized2_header_size(g);
+ size_t gsize = gserialized2_buffer_size(g);
+ if (gsize < hsz + sizeof(uint32_t))
+ {
+ lwerror("%s: GSERIALIZED too small for geometry type", __func__);
+ return 0;
+ }
return *((uint32_t*)(ptr));
}
int32_t gserialized2_get_srid(const GSERIALIZED *g)
{
- int32_t srid = 0;
- srid = srid | (g->srid[0] << 16);
- srid = srid | (g->srid[1] << 8);
- srid = srid | (g->srid[2]);
- /* Only the first 21 bits are set. Slide up and back to pull
- the negative bits down, if we need them. */
- srid = (srid<<11)>>11;
+ uint32_t srid = 0;
+ srid = srid | ((uint32_t)g->srid[0] << 16);
+ srid = srid | ((uint32_t)g->srid[1] << 8);
+ srid = srid | (uint32_t)g->srid[2];
+ /* Only the first 21 bits are set. Sign-extend without signed shift UB. */
+ if (srid & 0x00100000)
+ srid |= 0xFFE00000;
/* 0 is our internal unknown value. We'll map back and forth here for now */
if (srid == 0)
return SRID_UNKNOWN;
else
- return srid;
+ return (int32_t)srid;
}
void gserialized2_set_srid(GSERIALIZED *g, int32_t srid)
@@ -221,6 +258,235 @@ void gserialized2_set_srid(GSERIALIZED *g, int32_t srid)
g->srid[2] = (srid & 0x000000FF);
}
+static int
+gserialized2_range_available(const uint8_t *ptr, const uint8_t *end, size_t len)
+{
+ return ptr <= end && len <= (size_t)(end - ptr);
+}
+
+static int
+gserialized2_checked_mul(size_t a, size_t b, size_t *out)
+{
+ if (a != 0 && b > SIZE_MAX / a)
+ return LW_FAILURE;
+ *out = a * b;
+ return LW_SUCCESS;
+}
+
+static int
+gserialized2_checked_add(size_t a, size_t b, size_t *out)
+{
+ if (b > SIZE_MAX - a)
+ return LW_FAILURE;
+ *out = a + b;
+ return LW_SUCCESS;
+}
+
+static int
+gserialized2_payload_bounds(const GSERIALIZED *g, uint8_t **start, uint8_t **end)
+{
+ size_t hsz;
+ size_t gsize;
+
+ if (!g)
+ return LW_FAILURE;
+
+ hsz = gserialized2_header_size(g);
+ gsize = gserialized2_buffer_size(g);
+ if (gsize < hsz)
+ return LW_FAILURE;
+
+ if (start)
+ *start = (uint8_t *)g + hsz;
+ if (end)
+ *end = (uint8_t *)g + gsize;
+ return LW_SUCCESS;
+}
+
+static uint32_t
+gserialized2_read_uint32_checked(uint8_t *ptr, uint8_t *end, const char *field)
+{
+ uint32_t value = 0;
+ if (!gserialized2_range_available(ptr, end, sizeof(uint32_t)))
+ {
+ lwerror("%s: GSERIALIZED too small for %s", __func__, field);
+ return 0;
+ }
+ memcpy(&value, ptr, sizeof(uint32_t));
+ return value;
+}
+
+static int
+gserialized2_pointarray_payload_size(uint32_t npoints, lwflags_t lwflags, size_t *nbytes)
+{
+ if (gserialized2_checked_mul((size_t)npoints, sizeof(double) * FLAGS_NDIMS(lwflags), nbytes) == LW_FAILURE)
+ {
+ lwerror("%s: GSERIALIZED point count overflows payload size", __func__);
+ return LW_FAILURE;
+ }
+ return LW_SUCCESS;
+}
+
+static int
+gserialized2_validate_geometry_buffer(uint8_t *data_ptr, uint8_t *data_end, lwflags_t lwflags, size_t *size)
+{
+ uint32_t type, count;
+ size_t consumed = 0;
+
+ if (!gserialized2_range_available(data_ptr, data_end, 2 * sizeof(uint32_t)))
+ {
+ lwerror("%s: GSERIALIZED geometry payload is too short", __func__);
+ return LW_FAILURE;
+ }
+
+ type = gserialized2_read_uint32_checked(data_ptr, data_end, "geometry type");
+ count = gserialized2_read_uint32_checked(data_ptr + sizeof(uint32_t), data_end, "geometry count");
+
+ switch (type)
+ {
+ case POINTTYPE:
+ if (count > 1)
+ {
+ lwerror("%s: invalid point count %u", __func__, count);
+ return LW_FAILURE;
+ }
+ /* fall through */
+ case LINETYPE:
+ case CIRCSTRINGTYPE:
+ case TRIANGLETYPE: {
+ size_t point_bytes;
+ if (gserialized2_pointarray_payload_size(count, lwflags, &point_bytes) == LW_FAILURE)
+ return LW_FAILURE;
+ consumed = 2 * sizeof(uint32_t) + point_bytes;
+ break;
+ }
+
+ case POLYGONTYPE: {
+ size_t ring_counts_size, ring_counts_padded_size, point_bytes_sum = 0;
+ uint8_t *ring_counts = data_ptr + 2 * sizeof(uint32_t);
+ uint32_t i;
+
+ if (gserialized2_checked_mul((size_t)count, sizeof(uint32_t), &ring_counts_size) == LW_FAILURE)
+ {
+ lwerror("%s: GSERIALIZED ring count overflows payload size", __func__);
+ return LW_FAILURE;
+ }
+ ring_counts_padded_size = ring_counts_size + ((count % 2) ? sizeof(uint32_t) : 0);
+ if (!gserialized2_range_available(ring_counts, data_end, ring_counts_padded_size))
+ {
+ lwerror("%s: GSERIALIZED polygon ring table exceeds payload size", __func__);
+ return LW_FAILURE;
+ }
+
+ for (i = 0; i < count; i++)
+ {
+ size_t ring_point_bytes;
+ uint32_t npoints = gserialized2_read_uint32_checked(
+ ring_counts + i * sizeof(uint32_t), data_end, "ring point count");
+ if (gserialized2_pointarray_payload_size(npoints, lwflags, &ring_point_bytes) == LW_FAILURE)
+ return LW_FAILURE;
+ if (gserialized2_checked_add(point_bytes_sum, ring_point_bytes, &point_bytes_sum) == LW_FAILURE)
+ {
+ lwerror("%s: GSERIALIZED polygon coordinate size overflows", __func__);
+ return LW_FAILURE;
+ }
+ }
+ consumed = 2 * sizeof(uint32_t) + ring_counts_padded_size;
+ if (gserialized2_checked_add(consumed, point_bytes_sum, &consumed) == LW_FAILURE)
+ {
+ lwerror("%s: GSERIALIZED polygon size overflows", __func__);
+ return LW_FAILURE;
+ }
+ break;
+ }
+
+ case MULTIPOINTTYPE:
+ case MULTILINETYPE:
+ case MULTIPOLYGONTYPE:
+ case COMPOUNDTYPE:
+ case CURVEPOLYTYPE:
+ case MULTICURVETYPE:
+ case MULTISURFACETYPE:
+ case POLYHEDRALSURFACETYPE:
+ case TINTYPE:
+ case COLLECTIONTYPE: {
+ uint32_t i;
+ uint8_t *subgeom_ptr = data_ptr + 2 * sizeof(uint32_t);
+ lwflags_t subflags = lwflags;
+ FLAGS_SET_BBOX(subflags, 0);
+ for (i = 0; i < count; i++)
+ {
+ size_t subsize = 0;
+ uint32_t subtype =
+ gserialized2_read_uint32_checked(subgeom_ptr, data_end, "collection subtype");
+ if (!lwcollection_allows_subtype(type, subtype))
+ {
+ lwerror("Invalid subtype (%s) for collection type (%s)",
+ lwtype_name(subtype),
+ lwtype_name(type));
+ return LW_FAILURE;
+ }
+ if (gserialized2_validate_geometry_buffer(subgeom_ptr, data_end, subflags, &subsize) ==
+ LW_FAILURE)
+ return LW_FAILURE;
+ if (subsize == 0)
+ {
+ lwerror("%s: GSERIALIZED collection member has zero size", __func__);
+ return LW_FAILURE;
+ }
+ subgeom_ptr += subsize;
+ }
+ consumed = (size_t)(subgeom_ptr - data_ptr);
+ break;
+ }
+
+ case NURBSCURVETYPE: {
+ uint32_t nweights, nknots;
+ size_t weight_bytes, knot_bytes, point_bytes;
+ consumed = 6 * sizeof(uint32_t);
+ if (!gserialized2_range_available(data_ptr, data_end, consumed))
+ {
+ lwerror("%s: GSERIALIZED NURBS header exceeds payload size", __func__);
+ return LW_FAILURE;
+ }
+ nweights =
+ gserialized2_read_uint32_checked(data_ptr + 3 * sizeof(uint32_t), data_end, "NURBS weight count");
+ nknots =
+ gserialized2_read_uint32_checked(data_ptr + 4 * sizeof(uint32_t), data_end, "NURBS knot count");
+ if (gserialized2_checked_mul((size_t)nweights, sizeof(double), &weight_bytes) == LW_FAILURE ||
+ gserialized2_checked_mul((size_t)nknots, sizeof(double), &knot_bytes) == LW_FAILURE)
+ {
+ lwerror("%s: GSERIALIZED NURBS vector size overflows", __func__);
+ return LW_FAILURE;
+ }
+ if (gserialized2_pointarray_payload_size(count, lwflags, &point_bytes) == LW_FAILURE)
+ return LW_FAILURE;
+ if (gserialized2_checked_add(consumed, weight_bytes, &consumed) == LW_FAILURE ||
+ gserialized2_checked_add(consumed, knot_bytes, &consumed) == LW_FAILURE ||
+ gserialized2_checked_add(consumed, point_bytes, &consumed) == LW_FAILURE)
+ {
+ lwerror("%s: GSERIALIZED NURBS size overflows", __func__);
+ return LW_FAILURE;
+ }
+ break;
+ }
+
+ default:
+ lwerror("Unknown geometry type: %d - %s", type, lwtype_name(type));
+ return LW_FAILURE;
+ }
+
+ if (!gserialized2_range_available(data_ptr, data_end, consumed))
+ {
+ lwerror("%s: GSERIALIZED geometry payload exceeds declared size", __func__);
+ return LW_FAILURE;
+ }
+
+ if (size)
+ *size = consumed;
+ return LW_SUCCESS;
+}
+
static size_t gserialized2_is_empty_recurse(const uint8_t *p, int *isempty);
static size_t gserialized2_is_empty_recurse(const uint8_t *p, int *isempty)
{
@@ -285,7 +551,14 @@ static size_t gserialized2_is_empty_recurse(const uint8_t *p, int *isempty)
int gserialized2_is_empty(const GSERIALIZED *g)
{
int isempty = LW_TRUE;
- uint8_t *p = gserialized2_get_geometry_p(g);
+ uint8_t *p, *end;
+ if (gserialized2_payload_bounds(g, &p, &end) == LW_FAILURE)
+ {
+ lwerror("%s: invalid GSERIALIZED header size", __func__);
+ return LW_TRUE;
+ }
+ if (gserialized2_validate_geometry_buffer(p, end, gserialized2_get_lwflags(g), NULL) == LW_FAILURE)
+ return LW_TRUE;
gserialized2_is_empty_recurse(p, &isempty);
return isempty;
}
@@ -333,13 +606,21 @@ const float * gserialized2_get_float_box_p(const GSERIALIZED *g, size_t *ndims)
uint8_t *ptr = (uint8_t*)(g->data);
size_t bndims = G2FLAGS_NDIMS_BOX(g->gflags);
+ size_t box_offset = offsetof(GSERIALIZED, data);
+ size_t box_size = gserialized2_box_size(g);
if (ndims)
*ndims = bndims;
/* Advance past optional extended flags */
if (gserialized2_has_extended(g))
+ {
ptr += 8;
+ box_offset += 8;
+ }
+
+ if (gserialized2_buffer_size(g) < box_offset + box_size)
+ return NULL;
return (const float *)(ptr);
}
@@ -359,6 +640,8 @@ int gserialized2_read_gbox_p(const GSERIALIZED *g, GBOX *gbox)
{
int i = 0;
const float *fbox = gserialized2_get_float_box_p(g, NULL);
+ if (!fbox)
+ return LW_FAILURE;
gbox->xmin = fbox[i++];
gbox->xmax = fbox[i++];
gbox->ymin = fbox[i++];
@@ -394,10 +677,20 @@ int gserialized2_read_gbox_p(const GSERIALIZED *g, GBOX *gbox)
int
gserialized2_peek_gbox_p(const GSERIALIZED *g, GBOX *gbox)
{
- uint32_t type = gserialized2_get_type(g);
- uint8_t *geometry_start = gserialized2_get_geometry_p(g);
- double *dptr = (double *)(geometry_start);
- int32_t *iptr = (int32_t *)(geometry_start);
+ uint32_t type;
+ uint8_t *geometry_start = NULL;
+ uint8_t *geometry_end = NULL;
+ double *dptr = NULL;
+ int32_t *iptr = NULL;
+
+ if (gserialized2_payload_bounds(g, &geometry_start, &geometry_end) == LW_FAILURE)
+ return LW_FAILURE;
+ if (gserialized2_validate_geometry_buffer(geometry_start, geometry_end, gserialized2_get_lwflags(g), NULL) ==
+ LW_FAILURE)
+ return LW_FAILURE;
+ type = gserialized2_get_type(g);
+ dptr = (double *)(geometry_start);
+ iptr = (int32_t *)(geometry_start);
/* Peeking doesn't help if you already have a box or are geodetic */
if (G2FLAGS_GET_GEODETIC(g->gflags) || G2FLAGS_GET_BBOX(g->gflags))
@@ -582,7 +875,14 @@ gserialized2_copy_point(double *dptr, lwflags_t flags, POINT4D *out_point)
int
gserialized2_peek_first_point(const GSERIALIZED *g, POINT4D *out_point)
{
- uint8_t *geometry_start = gserialized2_get_geometry_p(g);
+ uint8_t *geometry_start = NULL;
+ uint8_t *geometry_end = NULL;
+
+ if (gserialized2_payload_bounds(g, &geometry_start, &geometry_end) == LW_FAILURE)
+ return LW_FAILURE;
+ if (gserialized2_validate_geometry_buffer(geometry_start, geometry_end, gserialized2_get_lwflags(g), NULL) ==
+ LW_FAILURE)
+ return LW_FAILURE;
uint32_t isEmpty = (((uint32_t *)geometry_start)[1]) == 0;
if (isEmpty)
@@ -1541,22 +1841,19 @@ LWGEOM* lwgeom_from_gserialized2(const GSERIALIZED *g)
assert(g);
srid = gserialized2_get_srid(g);
- lwtype = gserialized2_get_type(g);
lwflags = gserialized2_get_lwflags(g);
- LWDEBUGF(4, "Got type %d (%s), srid=%d", lwtype, lwtype_name(lwtype), srid);
-
- data_ptr = (uint8_t*)g->data;
-
- /* Skip optional flags */
- if (G2FLAGS_GET_EXTENDED(g->gflags))
+ if (gserialized2_payload_bounds(g, &data_ptr, NULL) == LW_FAILURE)
{
- data_ptr += sizeof(uint64_t);
+ lwerror("%s: invalid GSERIALIZED header size", __func__);
+ return NULL;
}
+ if (gserialized2_validate_geometry_buffer(
+ data_ptr, (uint8_t *)g + gserialized2_buffer_size(g), lwflags, NULL) == LW_FAILURE)
+ return NULL;
+ lwtype = gserialized2_get_type(g);
- /* Skip over optional bounding box */
- if (FLAGS_GET_BBOX(lwflags))
- data_ptr += gbox_serialized_size(lwflags);
+ LWDEBUGF(4, "Got type %d (%s), srid=%d", lwtype, lwtype_name(lwtype), srid);
lwgeom = lwgeom_from_gserialized2_buffer(data_ptr, lwflags, &size, srid);
-----------------------------------------------------------------------
Summary of changes:
.woodpecker/regress.yml | 5 +-
NEWS | 6 +
doc/reference_processing.xml | 1 +
liblwgeom/cunit/cu_gserialized1.c | 42 +++++
liblwgeom/cunit/cu_gserialized2.c | 131 ++++++++++++++
liblwgeom/gserialized1.c | 301 +++++++++++++++++++++++++++++---
liblwgeom/gserialized2.c | 316 +++++++++++++++++++++++++++++++---
postgis/lwgeom_geos.c | 47 ++++-
raster/rt_core/rt_band.c | 2 +
raster/rt_pg/rtpg_raster_properties.c | 8 +-
regress/core/geos39.sql | 6 +
regress/core/geos39_expected | 1 +
regress/core/interrupt_relate.sql | 16 +-
13 files changed, 817 insertions(+), 65 deletions(-)
hooks/post-receive
--
PostGIS
More information about the postgis-tickets
mailing list