[SCM] PostGIS branch master updated. 3.6.0rc2-547-g6f66e39d8

git at osgeo.org git at osgeo.org
Wed Jun 17 15:06:46 PDT 2026


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "PostGIS".

The branch, master has been updated
       via  6f66e39d8aaf7261bcf4f5b7ce10c9b727fc7168 (commit)
       via  73871012be232126e6fcb6eb22009842e9b6d9a7 (commit)
      from  5ec62d6985632cc603289b2e7af5efa693d683ca (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 6f66e39d8aaf7261bcf4f5b7ce10c9b727fc7168
Merge: 5ec62d698 73871012b
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Thu Jun 18 02:03:07 2026 +0400

    Merge PR #892: add TWKB and raster OSS-Fuzz targets
    
    Adds OSS-Fuzz harnesses for TWKB import and serialized raster deserialization, shared fuzzer allocation/error handling helpers, checked-in seed corpus archives, and build wiring for raster-enabled fuzz targets.
    
    Also hardens TWKB truncation, overlong varint, and impossible element-count handling covered by the new fuzzer and CUnit regression inputs.


commit 73871012be232126e6fcb6eb22009842e9b6d9a7
Author: Darafei Praliaskouski <me at komzpa.net>
Date:   Wed Jun 17 23:43:11 2026 +0400

    fuzzers: add TWKB and raster input targets

diff --git a/GNUmakefile.in b/GNUmakefile.in
index 8b007271c..e65fd23e4 100644
--- a/GNUmakefile.in
+++ b/GNUmakefile.in
@@ -83,6 +83,9 @@ check: check-unit docs-check check-lint check-spell
 ifeq (@SUPPORT_POSTGRESQL@,yes)
 check: check-regress
 endif
+ifeq (@RASTER@,raster)
+check: check-fuzzers
+endif
 
 # The "lint" tests do not depend on specific code configurations
 # but solely on the source code state
@@ -95,6 +98,16 @@ check-news:
 	$(top_srcdir)/utils/check_news.sh $(top_srcdir)
 
 check-unit check-regress: all
+ifeq (@RASTER@,raster)
+check-fuzzers: all
+	mkdir -p fuzzers
+	fuzzer_out=`mktemp -d "$${TMPDIR:-/tmp}/postgis-fuzzer-check.XXXXXX"`; \
+	trap 'rm -rf "$${fuzzer_out}"' EXIT HUP INT TERM; \
+	$(MAKE) -C fuzzers -f $(abspath $(top_srcdir))/fuzzers/Makefile \
+		POSTGIS_BUILD_DIR="$(CURDIR)" \
+		FUZZER_OUT="$${fuzzer_out}" \
+		check
+endif
 
 check-regress: export POSTGIS_REGRESS_DB ?= postgis_reg-$(POSTGIS_MAJOR_VERSION).$(POSTGIS_MINOR_VERSION)
 
diff --git a/NEWS b/NEWS
index 54811e708..4cd72746a 100644
--- a/NEWS
+++ b/NEWS
@@ -52,6 +52,8 @@ To take advantage of all postgis_sfcgal extension features SFCGAL 2.3+ is needed
 
  - GH-885, [topology] Avoid GMP overflow in ring orientation for very large
           finite coordinates (Darafei Praliaskouski)
+ - GH-892, Add OSS-Fuzz coverage for TWKB and serialized raster inputs,
+          including guards for malformed TWKB reads and counts (Darafei Praliaskouski)
  - Build PostgreSQL extension modules with `-fno-semantic-interposition` when
           available so LTO can optimize same-DSO calls to exported PostGIS
           entry points directly instead of routing through the module PLT; in
diff --git a/fuzzers/Makefile b/fuzzers/Makefile
index 5eeb2438e..01b324312 100644
--- a/fuzzers/Makefile
+++ b/fuzzers/Makefile
@@ -1,10 +1,81 @@
+FUZZER_SRC_DIR ?= $(abspath $(dir $(lastword $(MAKEFILE_LIST))))
+POSTGIS_BUILD_DIR ?= $(abspath ..)
+FUZZER_OUT ?= /tmp
+FUZZER_WORK ?= $(FUZZER_OUT)/postgis-fuzzer-check
+FUZZER_SOURCES := $(notdir $(wildcard $(FUZZER_SRC_DIR)/*_fuzzer.cpp $(FUZZER_SRC_DIR)/*_fuzzer.c))
+FUZZER_NAMES := $(basename $(FUZZER_SOURCES))
+SEED_CORPORA := $(wildcard $(FUZZER_SRC_DIR)/*_seed_corpus.zip)
+POSTGIS_CONFIGURED_LDFLAGS := $(shell sed -n 's/^LDFLAGS = //p' $(POSTGIS_BUILD_DIR)/liblwgeom/Makefile 2>/dev/null | sed 1q)
+POSTGIS_FUZZER_LDFLAGS = $(POSTGIS_CONFIGURED_LDFLAGS) $(LDFLAGS)
+
+.PHONY: clean dummyfuzzers check check-corpus
+
 clean:
 	$(RM) -f *.o *.a
 
-fuzzingengine.o: fuzzingengine.c
+fuzzingengine.o: $(FUZZER_SRC_DIR)/fuzzingengine.c
 	$(CC) $(CFLAGS) -c -o $@ $<
 
 dummyfuzzers: fuzzingengine.o
+	mkdir -p "$(FUZZER_OUT)"
 	$(AR) r libFuzzingEngine.a fuzzingengine.o
-	CXX="${CXX}" CXXFLAGS="-L. ${CXXFLAGS}" SRC=/tmp OUT=/tmp ./build_google_oss_fuzzers.sh
-	OUT=/tmp ./build_seed_corpus.sh
+	CC="${CC}" CXX="${CXX}" CXXFLAGS="-L$(CURDIR) ${CXXFLAGS}" LDFLAGS="$(POSTGIS_FUZZER_LDFLAGS)" POSTGIS_BUILD_DIR="$(POSTGIS_BUILD_DIR)" SRC="$(FUZZER_SRC_DIR)" OUT="$(FUZZER_OUT)" $(FUZZER_SRC_DIR)/build_google_oss_fuzzers.sh
+	OUT="$(FUZZER_OUT)" $(FUZZER_SRC_DIR)/build_seed_corpus.sh
+
+check:
+	@cxx="$${CXX:-$(CXX)}"; \
+	set -- $${cxx}; \
+	if [ "$$#" -eq 0 ] || ! command -v "$$1" >/dev/null 2>&1 || ! "$$@" --version >/dev/null 2>&1; then \
+		echo "C++ compiler '$${cxx}' not available; skipping fuzzer smoke check."; \
+	elif ! command -v pkg-config >/dev/null 2>&1 || ! pkg-config --exists json-c proj libxml-2.0; then \
+		echo "pkg-config metadata for fuzzer dependencies not found; skipping fuzzer smoke check."; \
+	elif ! command -v geos-config >/dev/null 2>&1 || ! command -v gdal-config >/dev/null 2>&1; then \
+		echo "GEOS/GDAL fuzzer dependency helpers not found; skipping fuzzer smoke check."; \
+	else \
+		set -e; \
+		if { command -v python3 >/dev/null 2>&1 && python3 -m zipfile --help >/dev/null 2>&1; } || \
+		   { command -v unzip >/dev/null 2>&1 && unzip -v >/dev/null 2>&1; }; then \
+			$(MAKE) check-corpus; \
+		else \
+			echo "No seed corpus zip extractor found; running fuzzer smoke check without corpus replay."; \
+			$(MAKE) dummyfuzzers; \
+			for fuzzer in $(FUZZER_NAMES); do \
+				"$(FUZZER_OUT)/$${fuzzer}"; \
+			done; \
+		fi; \
+	fi
+
+check-corpus: dummyfuzzers
+	@set -e; \
+	for fuzzer in $(FUZZER_NAMES); do \
+		"$(FUZZER_OUT)/$${fuzzer}"; \
+	done
+	@set -e; \
+	if command -v python3 >/dev/null 2>&1 && python3 -m zipfile --help >/dev/null 2>&1; then \
+		extract_zip=python3; \
+	elif command -v unzip >/dev/null 2>&1 && unzip -v >/dev/null 2>&1; then \
+		extract_zip=unzip; \
+	else \
+		echo "No seed corpus zip extractor found. Install python3 or unzip, then rerun check-corpus."; \
+		exit 1; \
+	fi; \
+	rm -rf "$(FUZZER_WORK)"; \
+	mkdir -p "$(FUZZER_WORK)"; \
+	trap 'rm -rf "$(FUZZER_WORK)"' EXIT HUP INT TERM; \
+	for corpus in $(SEED_CORPORA) __none__; do \
+		if [ "$${corpus}" = "__none__" ]; then \
+			continue; \
+		fi; \
+		corpus_name=$$(basename "$${corpus}"); \
+		fuzzer=$${corpus_name%_seed_corpus.zip}; \
+		corpus_dir="$(FUZZER_WORK)/$${fuzzer}"; \
+		mkdir -p "$${corpus_dir}"; \
+		if [ "$${extract_zip}" = "python3" ]; then \
+			python3 -m zipfile -e "$${corpus}" "$${corpus_dir}"; \
+		else \
+			unzip -qq "$${corpus}" -d "$${corpus_dir}"; \
+		fi; \
+		find "$${corpus_dir}" -type f -print | while IFS= read -r seed; do \
+			"$(FUZZER_OUT)/$${fuzzer}" "$${seed}"; \
+		done; \
+	done
diff --git a/fuzzers/README.md b/fuzzers/README.md
index 0c167e4f3..738b91d5b 100644
--- a/fuzzers/README.md
+++ b/fuzzers/README.md
@@ -27,12 +27,16 @@ The `oss-fuzz` project `Dockerfile` should call this script during image build.
 
 ## Local Workflow
 
-### Simulate Dummy Fuzzer Build
+### Simulate Dummy Fuzzer Build And Check
 
 ```bash
-make dummyfuzzers
+make check
 ```
 
+When `python3` or `unzip` is available, this also replays the checked-in seed
+corpus archives. Hosts without an archive extractor still run a no-input smoke
+check for each fuzzer.
+
 Artifacts are created in `/tmp`:
 
 - `/tmp/*_fuzzer`
@@ -45,6 +49,12 @@ Run one fuzzer locally:
 /tmp/wkt_import_fuzzer a_file_name
 ```
 
+Require checked-in seed corpus replay:
+
+```bash
+make check-corpus
+```
+
 ### Run OSS-Fuzz Locally
 
 ```bash
diff --git a/fuzzers/build_google_oss_fuzzers.sh b/fuzzers/build_google_oss_fuzzers.sh
index 4f1435c24..1cc825413 100755
--- a/fuzzers/build_google_oss_fuzzers.sh
+++ b/fuzzers/build_google_oss_fuzzers.sh
@@ -17,23 +17,65 @@ if [ "$CXX" == "" ]; then
     exit 1
 fi
 
-SRC_DIR=$(dirname $0)/..
+if [ "$CC" == "" ]; then
+    echo "CC env var not defined"
+    exit 1
+fi
+
+POSTGIS_SOURCE_DIR="${POSTGIS_SOURCE_DIR:-$(cd "$(dirname "$0")/.." && pwd)}"
+POSTGIS_BUILD_DIR="${POSTGIS_BUILD_DIR:-$POSTGIS_SOURCE_DIR}"
+FUZZERS_DIR="$POSTGIS_SOURCE_DIR/fuzzers"
 JSON_C_LIBS=$(pkg-config --libs json-c)
 GEOS_LIBS=$(geos-config --clibs)
 PROJ_XML2_LIBS=$(pkg-config --libs proj libxml-2.0)
+GDAL_CFLAGS=$(gdal-config --cflags)
+GDAL_LIBS=$(gdal-config --libs)
 POSTGIS_FUZZER_LIBS="$JSON_C_LIBS $GEOS_LIBS $PROJ_XML2_LIBS"
 POSTGIS_PACKAGE_RUNTIME_LIBS="${POSTGIS_PACKAGE_RUNTIME_LIBS:-1}"
 
+target_cflags()
+{
+    case "$1" in
+        raster_deserialize_fuzzer)
+            echo "-I$POSTGIS_SOURCE_DIR/raster/rt_core -I$POSTGIS_BUILD_DIR/raster/rt_core -I$POSTGIS_SOURCE_DIR/raster -I$POSTGIS_BUILD_DIR/raster -I$POSTGIS_SOURCE_DIR -I$POSTGIS_BUILD_DIR $GDAL_CFLAGS"
+            ;;
+    esac
+}
+
+target_libs()
+{
+    case "$1" in
+        raster_deserialize_fuzzer)
+            echo "$POSTGIS_BUILD_DIR/raster/rt_core/librtcore.a $GDAL_LIBS"
+            ;;
+    esac
+}
+
 build_fuzzer()
 {
     fuzzerName=$1
     sourceFilename=$2
-    shift
-    shift
+    extension=${sourceFilename##*.}
+    objectFile=""
+
     echo "Building fuzzer $fuzzerName"
-    $CXX $CXXFLAGS -std=c++11 -I$SRC_DIR/liblwgeom \
-        $sourceFilename $* -o $OUT/$fuzzerName \
-        -lFuzzingEngine -lstdc++ $SRC_DIR/liblwgeom/.libs/liblwgeom.a $POSTGIS_FUZZER_LIBS
+
+    if [ "$extension" = "c" ]; then
+        objectFile=$(mktemp)
+        "$CC" $CFLAGS -I"$POSTGIS_SOURCE_DIR/liblwgeom" -I"$POSTGIS_BUILD_DIR/liblwgeom" $(target_cflags "$fuzzerName") \
+            -c "$sourceFilename" -o "$objectFile"
+        sourceFilename=$objectFile
+    fi
+
+    "$CXX" $CXXFLAGS -std=c++11 -I"$POSTGIS_SOURCE_DIR/liblwgeom" -I"$POSTGIS_BUILD_DIR/liblwgeom" \
+        "$sourceFilename" -o "$OUT/$fuzzerName" \
+        $LDFLAGS \
+        -lFuzzingEngine -lstdc++ $(target_libs "$fuzzerName") \
+        "$POSTGIS_BUILD_DIR/liblwgeom/.libs/liblwgeom.a" $POSTGIS_FUZZER_LIBS
+
+    if [ "$objectFile" != "" ]; then
+        rm -f "$objectFile"
+    fi
 }
 
 package_runtime_libs()
@@ -66,13 +108,22 @@ package_runtime_libs()
     fi
 }
 
-fuzzerFiles=$(dirname $0)/*.cpp
-for F in $fuzzerFiles; do
-    fuzzerName=$(basename $F .cpp)
-    build_fuzzer $fuzzerName $F
+for F in "$FUZZERS_DIR"/*.cpp; do
+    [ -e "$F" ] || continue
+    fuzzerName=$(basename "$F" .cpp)
+    build_fuzzer "$fuzzerName" "$F"
 done
 
-cp $(dirname $0)/*.dict $(dirname $0)/*.options $(dirname $0)/*.zip $OUT/
+for F in "$FUZZERS_DIR"/*_fuzzer.c; do
+    [ -e "$F" ] || continue
+    fuzzerName=$(basename "$F" .c)
+    build_fuzzer "$fuzzerName" "$F"
+done
+
+for artifact in "$FUZZERS_DIR"/*.dict "$FUZZERS_DIR"/*.options "$FUZZERS_DIR"/*.zip; do
+    [ -e "$artifact" ] || continue
+    cp "$artifact" "$OUT/"
+done
 
 if [ "$POSTGIS_PACKAGE_RUNTIME_LIBS" != "0" ]; then
     package_runtime_libs
diff --git a/fuzzers/build_oss_fuzz.sh b/fuzzers/build_oss_fuzz.sh
index 5b65ddcda..b733bf1a8 100755
--- a/fuzzers/build_oss_fuzz.sh
+++ b/fuzzers/build_oss_fuzz.sh
@@ -28,7 +28,7 @@ fi
 export CXXFLAGS="$CXXFLAGS -std=c++11"
 POSTGIS_OSS_FUZZ_CONFIGURE_FLAGS=(
     --enable-static
-    --without-raster
+    --with-raster
     --without-protobuf
     --enable-debug
 )
@@ -36,10 +36,8 @@ POSTGIS_OSS_FUZZ_CONFIGURE_FLAGS=(
 cd "$SRC_DIR"
 ./autogen.sh
 ./configure CC="$CC" CXX="$CXX" "${POSTGIS_OSS_FUZZ_CONFIGURE_FLAGS[@]}"
-cd liblwgeom
-make clean -s
-make -j"$(nproc)" -s
-cd ..
+make -C liblwgeom -j"$(nproc)" -s
+make -C raster corelib -j"$(nproc)" -s
 
 bash ./fuzzers/build_google_oss_fuzzers.sh
 bash ./fuzzers/build_seed_corpus.sh
diff --git a/fuzzers/install_oss_fuzz_build_deps.sh b/fuzzers/install_oss_fuzz_build_deps.sh
index dd3475745..2740687f4 100755
--- a/fuzzers/install_oss_fuzz_build_deps.sh
+++ b/fuzzers/install_oss_fuzz_build_deps.sh
@@ -17,7 +17,7 @@ export DEBIAN_FRONTEND="${DEBIAN_FRONTEND:-noninteractive}"
 apt-get update
 apt-get install -y --no-install-recommends \
     make autoconf automake libtool bison flex g++ postgresql-server-dev-all \
-    libgeos-dev libproj-dev libxml2-dev pkg-config libjson-c-dev \
-    libc++-dev libc++abi-dev patchelf
+    libgeos-dev libproj-dev libxml2-dev pkg-config libjson-c-dev libgmp-dev \
+    libgdal-dev libc++-dev libc++abi-dev patchelf
 
 rm -rf /var/lib/apt/lists/*
diff --git a/fuzzers/liblwgeom_fuzzer.hpp b/fuzzers/liblwgeom_fuzzer.hpp
new file mode 100644
index 000000000..c44f6e526
--- /dev/null
+++ b/fuzzers/liblwgeom_fuzzer.hpp
@@ -0,0 +1,164 @@
+/******************************************************************************
+ *
+ * Project:  PostGIS
+ * Purpose:  Shared liblwgeom fuzzer support
+ *
+ ******************************************************************************
+ * Copyright (C) 2026 Darafei Praliaskouski <me at komzpa.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ ****************************************************************************/
+
+#ifndef POSTGIS_LIBLWGEOM_FUZZER_HPP
+#define POSTGIS_LIBLWGEOM_FUZZER_HPP
+
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+
+extern "C" {
+#include "liblwgeom.h"
+}
+
+static jmp_buf postgis_lwgeom_fuzzer_jmp_buf;
+static void **postgis_lwgeom_fuzzer_ptrs = NULL;
+static size_t postgis_lwgeom_fuzzer_count = 0;
+static size_t postgis_lwgeom_fuzzer_capacity = 0;
+
+#define POSTGIS_LWGEOM_FUZZER_SETJMP() setjmp(postgis_lwgeom_fuzzer_jmp_buf)
+
+static int
+postgis_lwgeom_fuzzer_track_add(void *ptr)
+{
+	void **new_ptrs;
+	size_t new_capacity;
+
+	if (postgis_lwgeom_fuzzer_count == postgis_lwgeom_fuzzer_capacity)
+	{
+		new_capacity = postgis_lwgeom_fuzzer_capacity ? postgis_lwgeom_fuzzer_capacity * 2 : 64;
+		new_ptrs = (void **)realloc(postgis_lwgeom_fuzzer_ptrs, new_capacity * sizeof(void *));
+		if (new_ptrs == NULL)
+			return 0;
+		postgis_lwgeom_fuzzer_ptrs = new_ptrs;
+		postgis_lwgeom_fuzzer_capacity = new_capacity;
+	}
+
+	postgis_lwgeom_fuzzer_ptrs[postgis_lwgeom_fuzzer_count++] = ptr;
+	return 1;
+}
+
+static void
+postgis_lwgeom_fuzzer_track_remove(void *ptr)
+{
+	for (size_t i = 0; i < postgis_lwgeom_fuzzer_count; i++)
+	{
+		if (postgis_lwgeom_fuzzer_ptrs[i] == ptr)
+		{
+			postgis_lwgeom_fuzzer_ptrs[i] = postgis_lwgeom_fuzzer_ptrs[--postgis_lwgeom_fuzzer_count];
+			return;
+		}
+	}
+}
+
+static void
+postgis_lwgeom_fuzzer_cleanup_allocations(void)
+{
+	for (size_t i = 0; i < postgis_lwgeom_fuzzer_count; i++)
+		free(postgis_lwgeom_fuzzer_ptrs[i]);
+	postgis_lwgeom_fuzzer_count = 0;
+}
+
+static void *
+postgis_lwgeom_fuzzer_malloc(size_t size)
+{
+	void *ptr = malloc(size);
+	if (ptr != NULL && !postgis_lwgeom_fuzzer_track_add(ptr))
+	{
+		free(ptr);
+		return NULL;
+	}
+	return ptr;
+}
+
+static void *
+postgis_lwgeom_fuzzer_realloc(void *ptr, size_t size)
+{
+	void *new_ptr;
+
+	if (ptr == NULL)
+		return postgis_lwgeom_fuzzer_malloc(size);
+	if (size == 0)
+	{
+		postgis_lwgeom_fuzzer_track_remove(ptr);
+		free(ptr);
+		return NULL;
+	}
+
+	new_ptr = realloc(ptr, size);
+	if (new_ptr == NULL)
+		return NULL;
+
+	for (size_t i = 0; i < postgis_lwgeom_fuzzer_count; i++)
+	{
+		if (postgis_lwgeom_fuzzer_ptrs[i] == ptr)
+		{
+			postgis_lwgeom_fuzzer_ptrs[i] = new_ptr;
+			return new_ptr;
+		}
+	}
+
+	if (!postgis_lwgeom_fuzzer_track_add(new_ptr))
+	{
+		free(new_ptr);
+		return NULL;
+	}
+	return new_ptr;
+}
+
+static void
+postgis_lwgeom_fuzzer_free(void *ptr)
+{
+	if (ptr == NULL)
+		return;
+	postgis_lwgeom_fuzzer_track_remove(ptr);
+	free(ptr);
+}
+
+extern "C" {
+static void
+postgis_lwgeom_fuzzer_noticereporter(const char *, va_list)
+{}
+
+static void
+postgis_lwgeom_fuzzer_errorreporter(const char *, va_list)
+{
+	postgis_lwgeom_fuzzer_cleanup_allocations();
+	longjmp(postgis_lwgeom_fuzzer_jmp_buf, 1);
+}
+
+static void
+postgis_lwgeom_fuzzer_debuglogger(int, const char *, va_list)
+{}
+}
+
+static void
+postgis_lwgeom_fuzzer_initialize(void)
+{
+	/* liblwgeom reports parser errors through the configured error reporter.
+	 * Track liblwgeom allocations so longjmp error paths release allocations
+	 * made before the jump.
+	 */
+	lwgeom_set_handlers(postgis_lwgeom_fuzzer_malloc,
+			    postgis_lwgeom_fuzzer_realloc,
+			    postgis_lwgeom_fuzzer_free,
+			    postgis_lwgeom_fuzzer_errorreporter,
+			    postgis_lwgeom_fuzzer_noticereporter);
+	lwgeom_set_debuglogger(postgis_lwgeom_fuzzer_debuglogger);
+}
+
+#endif
diff --git a/fuzzers/raster_deserialize_fuzzer.c b/fuzzers/raster_deserialize_fuzzer.c
new file mode 100644
index 000000000..cb69254f0
--- /dev/null
+++ b/fuzzers/raster_deserialize_fuzzer.c
@@ -0,0 +1,185 @@
+/******************************************************************************
+ *
+ * Project:  PostGIS
+ * Purpose:  Raster serialized input fuzzer
+ *
+ ******************************************************************************
+ * Copyright (C) 2026 Darafei Praliaskouski <me at komzpa.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ ****************************************************************************/
+
+#include <setjmp.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "librtcore.h"
+#include "rt_serialize.h"
+
+static jmp_buf jmpBuf;
+static void *current_serialized = NULL;
+
+static void
+cleanup_serialized(void)
+{
+	free(current_serialized);
+	current_serialized = NULL;
+}
+
+static void
+rt_message_handler_noop(const char *fmt, va_list ap)
+{
+	(void)fmt;
+	(void)ap;
+}
+
+static void
+rt_error_handler(const char *fmt, va_list ap)
+{
+	(void)fmt;
+	(void)ap;
+	cleanup_serialized();
+	longjmp(jmpBuf, 1);
+}
+
+int
+LLVMFuzzerInitialize(int *argc, char ***argv)
+{
+	(void)argc;
+	(void)argv;
+	rt_set_handlers(malloc, realloc, free, rt_error_handler, rt_message_handler_noop, rt_message_handler_noop);
+	return 0;
+}
+
+static int
+checked_add(size_t *total, size_t value, size_t limit)
+{
+	if (value > limit || *total > limit - value)
+		return 0;
+	*total += value;
+	return 1;
+}
+
+static int
+has_complete_serialized_raster(const uint8_t *buf, size_t len)
+{
+	struct rt_raster_serialized_t header;
+	size_t pos = sizeof(header);
+
+	/* rt_raster_deserialize() accepts a pointer but no buffer length. Before
+	 * calling it, walk the serialized layout far enough to prove each band,
+	 * nodata value, optional out-db path, pixel payload, and 8-byte padding byte
+	 * is inside the fuzzer-provided buffer.
+	 */
+	if (len < sizeof(header))
+		return 0;
+
+	memcpy(&header, buf, sizeof(header));
+
+	if (header.numBands > 256)
+		return 0;
+
+	for (uint16_t i = 0; i < header.numBands; i++)
+	{
+		if (pos >= len)
+			return 0;
+
+		const uint8_t band_type = buf[pos];
+		const rt_pixtype pixtype = (rt_pixtype)(band_type & BANDTYPE_PIXTYPE_MASK);
+		const int pixbytes = rt_pixtype_size(pixtype);
+		if (pixbytes <= 0)
+			return 0;
+
+		/* Band type byte, nodata padding bytes, and nodata value. */
+		if (!checked_add(&pos, 1, len))
+			return 0;
+		if (!checked_add(&pos, (size_t)(pixbytes - 1), len))
+			return 0;
+		if (!checked_add(&pos, (size_t)pixbytes, len))
+			return 0;
+
+		if (BANDTYPE_IS_OFFDB(band_type))
+		{
+			/* Out-db band number plus NUL-terminated path. */
+			if (!checked_add(&pos, 1, len))
+				return 0;
+
+			const void *nul = memchr(buf + pos, '\0', len - pos);
+			if (nul == NULL)
+				return 0;
+
+			const size_t path_len = (const uint8_t *)nul - (buf + pos);
+			if (!checked_add(&pos, path_len + 1, len))
+				return 0;
+		}
+		else
+		{
+			/* Inline raster data is width * height pixels in this band's
+			 * pixel type.
+			 */
+			const size_t pixels = (size_t)header.width * (size_t)header.height;
+			if (header.width != 0 && pixels / header.width != header.height)
+				return 0;
+			if (pixbytes > 0 && pixels > len / (size_t)pixbytes)
+				return 0;
+			if (!checked_add(&pos, pixels * (size_t)pixbytes, len))
+				return 0;
+		}
+
+		while (pos % 8 != 0)
+		{
+			if (!checked_add(&pos, 1, len))
+				return 0;
+		}
+	}
+
+	return 1;
+}
+
+static void
+destroy_raster_with_bands(rt_raster raster)
+{
+	uint16_t num_bands;
+
+	if (raster == NULL)
+		return;
+
+	num_bands = rt_raster_get_num_bands(raster);
+	for (uint16_t i = 0; i < num_bands; i++)
+	{
+		rt_band band = rt_raster_get_band(raster, i);
+		if (band != NULL)
+			rt_band_destroy(band);
+	}
+
+	rt_raster_destroy(raster);
+}
+
+int
+LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len)
+{
+	if (setjmp(jmpBuf))
+	{
+		cleanup_serialized();
+		return 0;
+	}
+
+	if (!has_complete_serialized_raster(buf, len))
+		return 0;
+
+	current_serialized = malloc(len);
+	if (current_serialized == NULL)
+		return 0;
+
+	memcpy(current_serialized, buf, len);
+	rt_raster raster = rt_raster_deserialize(current_serialized, 0);
+	destroy_raster_with_bands(raster);
+	cleanup_serialized();
+	return 0;
+}
diff --git a/fuzzers/raster_deserialize_fuzzer.options b/fuzzers/raster_deserialize_fuzzer.options
new file mode 100644
index 000000000..60a51fcba
--- /dev/null
+++ b/fuzzers/raster_deserialize_fuzzer.options
@@ -0,0 +1,2 @@
+[libfuzzer]
+max_len = 100000
diff --git a/fuzzers/raster_deserialize_fuzzer_seed_corpus.zip b/fuzzers/raster_deserialize_fuzzer_seed_corpus.zip
new file mode 100644
index 000000000..92e3dd60d
Binary files /dev/null and b/fuzzers/raster_deserialize_fuzzer_seed_corpus.zip differ
diff --git a/fuzzers/twkb_import_fuzzer.cpp b/fuzzers/twkb_import_fuzzer.cpp
new file mode 100644
index 000000000..d662667b2
--- /dev/null
+++ b/fuzzers/twkb_import_fuzzer.cpp
@@ -0,0 +1,56 @@
+/******************************************************************************
+ *
+ * Project:  PostGIS
+ * Purpose:  TWKB input fuzzer
+ *
+ ******************************************************************************
+ * Copyright (C) 2026 Darafei Praliaskouski <me at komzpa.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ ****************************************************************************/
+
+#include <stddef.h>
+#include <stdint.h>
+
+extern "C" {
+#include "geos_stub.h"
+#include "proj_stub.h"
+}
+
+#include "liblwgeom_fuzzer.hpp"
+
+extern "C" int
+LLVMFuzzerInitialize(int * /*argc*/, char *** /*argv*/)
+{
+	postgis_lwgeom_fuzzer_initialize();
+	return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len);
+
+int
+LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len)
+{
+	/* Avoid inputs too short to contain TWKB metadata. If the metadata says an
+	 * extended precision byte follows, make sure that byte exists too.
+	 */
+	if (len < 2)
+		return 0;
+	if ((buf[1] & 0x08) && len < 3)
+		return 0;
+
+	if (POSTGIS_LWGEOM_FUZZER_SETJMP())
+	{
+		postgis_lwgeom_fuzzer_cleanup_allocations();
+		return 0;
+	}
+
+	LWGEOM *lwgeom = lwgeom_from_twkb(buf, len, LW_PARSER_CHECK_NONE);
+	lwgeom_free(lwgeom);
+	postgis_lwgeom_fuzzer_cleanup_allocations();
+	return 0;
+}
diff --git a/fuzzers/twkb_import_fuzzer.options b/fuzzers/twkb_import_fuzzer.options
new file mode 100644
index 000000000..60a51fcba
--- /dev/null
+++ b/fuzzers/twkb_import_fuzzer.options
@@ -0,0 +1,2 @@
+[libfuzzer]
+max_len = 100000
diff --git a/fuzzers/twkb_import_fuzzer_seed_corpus.zip b/fuzzers/twkb_import_fuzzer_seed_corpus.zip
new file mode 100644
index 000000000..3c26fad6e
Binary files /dev/null and b/fuzzers/twkb_import_fuzzer_seed_corpus.zip differ
diff --git a/fuzzers/wkb_import_fuzzer.cpp b/fuzzers/wkb_import_fuzzer.cpp
index 48ff539ad..f1d1c7eb9 100644
--- a/fuzzers/wkb_import_fuzzer.cpp
+++ b/fuzzers/wkb_import_fuzzer.cpp
@@ -26,95 +26,33 @@
  * DEALINGS IN THE SOFTWARE.
  ****************************************************************************/
 
-#include <assert.h>
 #include <stddef.h>
 #include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
-#include <setjmp.h>
 
-#include <set>
-
-extern "C"
-{
-#include "liblwgeom.h"
+extern "C" {
 #include "geos_stub.h"
 #include "proj_stub.h"
 }
 
-extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv);
+#include "liblwgeom_fuzzer.hpp"
 
-// Keep active heap allocated memory corresponding to returns of allocator()
-// and reallocator()
-std::set<void*> oSetPointers;
-jmp_buf jmpBuf;
-
-extern "C"
+extern "C" int
+LLVMFuzzerInitialize(int * /*argc*/, char *** /*argv*/)
 {
-    static void *
-    allocator(size_t size)
-    {
-            void *mem = malloc(size);
-            oSetPointers.insert(mem);
-            return mem;
-    }
-
-    static void
-    freeor(void *mem)
-    {
-            oSetPointers.erase(mem);
-            free(mem);
-    }
-
-    static void *
-    reallocator(void *mem, size_t size)
-    {
-            oSetPointers.erase(mem);
-            void *ret = realloc(mem, size);
-            oSetPointers.insert(ret);
-            return ret;
-    }
-
-    static void
-    noticereporter(const char *, va_list )
-    {
-    }
-
-    static void
-    errorreporter(const char *, va_list )
-    {
-        // Cleanup any heap-allocated memory still active
-        for(std::set<void*>::iterator oIter = oSetPointers.begin();
-            oIter != oSetPointers.end(); ++oIter )
-        {
-            free(*oIter);
-        }
-        oSetPointers.clear();
-        // Abort everything to jump to setjmp() call
-        longjmp(jmpBuf, 1);
-    }
-
-    static void
-    debuglogger(int, const char *, va_list)
-    {
-    }
-
-}
-
-int LLVMFuzzerInitialize(int* /*argc*/, char*** /*argv*/)
-{
-	lwgeom_set_handlers(malloc, realloc, free, noticereporter, noticereporter);
-	lwgeom_set_debuglogger(debuglogger);
+	postgis_lwgeom_fuzzer_initialize();
 	return 0;
 }
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len);
 
 int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len)
 {
-    if( setjmp(jmpBuf) )
-        return 0;
-    LWGEOM* lwgeom = lwgeom_from_wkb(buf, len, LW_PARSER_CHECK_NONE);
-    lwgeom_free(lwgeom);
-    //assert( oSetPointers.empty() );
-    return 0;
+	if (POSTGIS_LWGEOM_FUZZER_SETJMP())
+	{
+		postgis_lwgeom_fuzzer_cleanup_allocations();
+		return 0;
+	}
+	LWGEOM *lwgeom = lwgeom_from_wkb(buf, len, LW_PARSER_CHECK_NONE);
+	lwgeom_free(lwgeom);
+	postgis_lwgeom_fuzzer_cleanup_allocations();
+	return 0;
 }
diff --git a/fuzzers/wkt_import_fuzzer.cpp b/fuzzers/wkt_import_fuzzer.cpp
index 46b514818..79c2dd49b 100644
--- a/fuzzers/wkt_import_fuzzer.cpp
+++ b/fuzzers/wkt_import_fuzzer.cpp
@@ -26,85 +26,22 @@
  * DEALINGS IN THE SOFTWARE.
  ****************************************************************************/
 
-#include <assert.h>
 #include <stddef.h>
 #include <stdint.h>
 #include <stdlib.h>
 #include <string.h>
-#include <setjmp.h>
 
-#include <set>
-
-extern "C"
-{
-#include "liblwgeom.h"
+extern "C" {
 #include "geos_stub.h"
 #include "proj_stub.h"
 }
 
-extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv);
+#include "liblwgeom_fuzzer.hpp"
 
-// Keep active heap allocated memory corresponding to returns of allocator()
-// and reallocator()
-std::set<void*> oSetPointers;
-jmp_buf jmpBuf;
-
-extern "C"
+extern "C" int
+LLVMFuzzerInitialize(int * /*argc*/, char *** /*argv*/)
 {
-    static void *
-    allocator(size_t size)
-    {
-            void *mem = malloc(size);
-            oSetPointers.insert(mem);
-            return mem;
-    }
-
-    static void
-    freeor(void *mem)
-    {
-            oSetPointers.erase(mem);
-            free(mem);
-    }
-
-    static void *
-    reallocator(void *mem, size_t size)
-    {
-            oSetPointers.erase(mem);
-            void *ret = realloc(mem, size);
-            oSetPointers.insert(ret);
-            return ret;
-    }
-
-    static void
-    noticereporter(const char *, va_list )
-    {
-    }
-
-    static void
-    errorreporter(const char *, va_list )
-    {
-        // Cleanup any heap-allocated memory still active
-        for(std::set<void*>::iterator oIter = oSetPointers.begin();
-            oIter != oSetPointers.end(); ++oIter )
-        {
-            free(*oIter);
-        }
-        oSetPointers.clear();
-        // Abort everything to jump to setjmp() call
-        longjmp(jmpBuf, 1);
-    }
-
-    static void
-    debuglogger(int, const char *, va_list)
-    {
-    }
-
-}
-
-int LLVMFuzzerInitialize(int* /*argc*/, char*** /*argv*/)
-{
-	lwgeom_set_handlers(malloc, realloc, free, noticereporter, noticereporter);
-	lwgeom_set_debuglogger(debuglogger);
+	postgis_lwgeom_fuzzer_initialize();
 	return 0;
 }
 
@@ -112,15 +49,18 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len);
 
 int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len)
 {
-    char* pszWKT = static_cast<char*>(malloc( len + 1 ));
-    memcpy(pszWKT, buf, len);
-    pszWKT[len] = '\0';
-    if( !setjmp(jmpBuf) )
-    {
-        LWGEOM* lwgeom = lwgeom_from_wkt(pszWKT, LW_PARSER_CHECK_NONE);
-        lwgeom_free(lwgeom);
-        //assert( oSetPointers.empty() );
-    }
-    free(pszWKT);
-    return 0;
+	char *pszWKT = static_cast<char *>(malloc(len + 1));
+	if (pszWKT == NULL)
+		return 0;
+
+	memcpy(pszWKT, buf, len);
+	pszWKT[len] = '\0';
+	if (!POSTGIS_LWGEOM_FUZZER_SETJMP())
+	{
+		LWGEOM *lwgeom = lwgeom_from_wkt(pszWKT, LW_PARSER_CHECK_NONE);
+		lwgeom_free(lwgeom);
+	}
+	postgis_lwgeom_fuzzer_cleanup_allocations();
+	free(pszWKT);
+	return 0;
 }
diff --git a/liblwgeom/cunit/cu_in_twkb.c b/liblwgeom/cunit/cu_in_twkb.c
index 02d2db374..5fb4197f0 100644
--- a/liblwgeom/cunit/cu_in_twkb.c
+++ b/liblwgeom/cunit/cu_in_twkb.c
@@ -3,6 +3,7 @@
  * PostGIS - Spatial Types for PostgreSQL
  * http://postgis.net
  * Copyright 2010 Paul Ramsey <pramsey at cleverelephant.ca>
+ * Copyright 2026 Darafei Praliaskouski <me at komzpa.net>
  *
  * This is free software; you can redistribute and/or modify it under
  * the terms of the GNU General Public Licence. See the COPYING file.
@@ -222,7 +223,88 @@ static void test_twkb_in_precision(void)
 	precision = 0;
 }
 
+static void
+test_twkb_in_truncated_extended_dims(void)
+{
+	const uint8_t twkb[] = {
+	    0x01, /* POINT with default precision. */
+	    0x18  /* Empty geometry plus extended-dimension byte follows. */
+	};
+	LWGEOM *geom;
 
+	cu_error_msg_reset();
+
+	geom = lwgeom_from_twkb(twkb, sizeof(twkb), LW_PARSER_CHECK_NONE);
+
+	/* Truncated extended-dimension metadata used to be read before the bounds
+	 * check. The expected contract is an ordinary TWKB size error, not an
+	 * out-of-buffer read while constructing the header.
+	 */
+	ASSERT_STRING_EQUAL(cu_error_msg, "twkb_parse_state_advance: TWKB structure does not match expected size!");
+	CU_ASSERT_PTR_NOT_NULL(geom);
+	if (geom != NULL)
+		lwgeom_free(geom);
+	cu_error_msg_reset();
+}
+
+static void
+test_twkb_in_overlong_varint(void)
+{
+	const uint8_t twkb[] = {0x02,
+				0x00, /* LINESTRING with default precision. */
+				0x80,
+				0x80,
+				0x80,
+				0x80,
+				0x80,
+				0x80, /* Overlong point-count varint. */
+				0x80,
+				0x80,
+				0x80,
+				0x80,
+				0x00};
+	LWGEOM *geom;
+
+	cu_error_msg_reset();
+
+	geom = lwgeom_from_twkb(twkb, sizeof(twkb), LW_PARSER_CHECK_NONE);
+
+	/* Varints longer than the uint64_t encoding space used to shift by more
+	 * than the C type width before the parser could report malformed input.
+	 */
+	ASSERT_STRING_EQUAL(cu_error_msg, "varint_u64_decode: varint exceeds 64 bits");
+	if (geom != NULL)
+		lwgeom_free(geom);
+	cu_error_msg_reset();
+}
+
+static void
+test_twkb_in_count_exceeds_payload(void)
+{
+	const uint8_t twkb[] = {0x02,
+				0x00, /* LINESTRING with default precision. */
+				0xff,
+				0xff,
+				0xff,
+				0xff,
+				0x0f, /* UINT32_MAX points. */
+				0x00,
+				0x00};
+	LWGEOM *geom;
+
+	cu_error_msg_reset();
+
+	geom = lwgeom_from_twkb(twkb, sizeof(twkb), LW_PARSER_CHECK_NONE);
+
+	/* Count fields are allocation sizes. Reject impossible counts before
+	 * constructing point arrays from a buffer that cannot contain them.
+	 */
+	ASSERT_STRING_EQUAL(cu_error_msg,
+			    "twkb_parse_state_has_min_bytes: TWKB element count exceeds remaining payload");
+	if (geom != NULL)
+		lwgeom_free(geom);
+	cu_error_msg_reset();
+}
 
 /*
 ** Used by test harness to register the tests in this file.
@@ -239,4 +321,7 @@ void twkb_in_suite_setup(void)
 	PG_ADD_TEST(suite, test_twkb_in_multipolygon);
 	PG_ADD_TEST(suite, test_twkb_in_collection);
 	PG_ADD_TEST(suite, test_twkb_in_precision);
+	PG_ADD_TEST(suite, test_twkb_in_truncated_extended_dims);
+	PG_ADD_TEST(suite, test_twkb_in_overlong_varint);
+	PG_ADD_TEST(suite, test_twkb_in_count_exceeds_payload);
 }
diff --git a/liblwgeom/lwin_twkb.c b/liblwgeom/lwin_twkb.c
index 72fc53306..1262dd999 100644
--- a/liblwgeom/lwin_twkb.c
+++ b/liblwgeom/lwin_twkb.c
@@ -2,6 +2,7 @@
  *
  * PostGIS - Spatial Types for PostgreSQL
  * http://postgis.net
+ * Copyright (C) 2026 Darafei Praliaskouski <me at komzpa.net>
  *
  * PostGIS is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -22,7 +23,6 @@
  *
  **********************************************************************/
 
-
 #include <math.h>
 #include "liblwgeom_internal.h"
 #include "lwgeom_log.h"
@@ -81,7 +81,7 @@ LWGEOM* lwgeom_from_twkb_state(twkb_parse_state *s);
 */
 static inline void twkb_parse_state_advance(twkb_parse_state *s, size_t next)
 {
-	if( (s->pos + next) > s->twkb_end)
+	if (next > (size_t)(s->twkb_end - s->pos))
 	{
 		lwerror("%s: TWKB structure does not match expected size!", __func__);
 		// lwnotice("TWKB structure does not match expected size!");
@@ -125,7 +125,37 @@ static inline void twkb_parse_state_varint_skip(twkb_parse_state *s)
 	return;
 }
 
+static inline uint32_t
+twkb_parse_state_uvarint32(twkb_parse_state *s)
+{
+	uint64_t val = twkb_parse_state_uvarint(s);
 
+	if (val > UINT32_MAX)
+	{
+		lwerror("%s: TWKB count exceeds uint32_t", __func__);
+		return 0;
+	}
+
+	return (uint32_t)val;
+}
+
+static inline int
+twkb_parse_state_has_min_bytes(twkb_parse_state *s, uint32_t count, size_t min_bytes)
+{
+	const size_t remaining = (size_t)(s->twkb_end - s->pos);
+
+	/* TWKB counts are trusted only after proving that the remaining buffer can
+	 * hold the smallest possible payload for that many elements. This rejects
+	 * impossible allocation requests before constructors size their arrays.
+	 */
+	if (min_bytes != 0 && count > remaining / min_bytes)
+	{
+		lwerror("%s: TWKB element count exceeds remaining payload", __func__);
+		return LW_FALSE;
+	}
+
+	return LW_TRUE;
+}
 
 static uint32_t lwtype_from_twkb_type(uint8_t twkb_type)
 {
@@ -159,7 +189,14 @@ static uint32_t lwtype_from_twkb_type(uint8_t twkb_type)
 */
 static uint8_t byte_from_twkb_state(twkb_parse_state *s)
 {
-	uint8_t val = *(s->pos);
+	uint8_t val = 0;
+
+	/* CUnit records lwerror and continues, while PostgreSQL and the fuzzers
+	 * abort through their error handlers. Keep the bounds check local so this
+	 * byte helper is safe under both execution models.
+	 */
+	if ((s->twkb_end - s->pos) >= WKB_BYTE_SIZE)
+		val = *(s->pos);
 	twkb_parse_state_advance(s, WKB_BYTE_SIZE);
 	return val;
 }
@@ -183,6 +220,9 @@ static POINTARRAY* ptarray_from_twkb_state(twkb_parse_state *s, uint32_t npoints
 	if( npoints == 0 )
 		return ptarray_construct_empty(s->has_z, s->has_m, 0);
 
+	if (!twkb_parse_state_has_min_bytes(s, npoints, s->ndims))
+		return NULL;
+
 	pa = ptarray_construct(s->has_z, s->has_m, npoints);
 	dlist = (double*)(pa->serialized_pointlist);
 	for( i = 0; i < npoints; i++ )
@@ -246,7 +286,7 @@ static LWLINE* lwline_from_twkb_state(twkb_parse_state *s)
 		return lwline_construct_empty(SRID_UNKNOWN, s->has_z, s->has_m);
 
 	/* Read number of points */
-	npoints = twkb_parse_state_uvarint(s);
+	npoints = twkb_parse_state_uvarint32(s);
 
 	if ( npoints == 0 )
 		return lwline_construct_empty(SRID_UNKNOWN, s->has_z, s->has_m);
@@ -281,7 +321,7 @@ static LWPOLY* lwpoly_from_twkb_state(twkb_parse_state *s)
 		return lwpoly_construct_empty(SRID_UNKNOWN, s->has_z, s->has_m);
 
 	/* Read number of rings */
-	nrings = twkb_parse_state_uvarint(s);
+	nrings = twkb_parse_state_uvarint32(s);
 
 	/* Start w/ empty polygon */
 	poly = lwpoly_construct_empty(SRID_UNKNOWN, s->has_z, s->has_m);
@@ -292,10 +332,13 @@ static LWPOLY* lwpoly_from_twkb_state(twkb_parse_state *s)
 	if( nrings == 0 )
 		return poly;
 
+	if (!twkb_parse_state_has_min_bytes(s, nrings, 1))
+		return poly;
+
 	for( i = 0; i < nrings; i++ )
 	{
 		/* Ret number of points */
-		uint32_t npoints = twkb_parse_state_uvarint(s);
+		uint32_t npoints = twkb_parse_state_uvarint32(s);
 		POINTARRAY *pa = ptarray_from_twkb_state(s, npoints);
 
 		/* Skip empty rings */
@@ -335,7 +378,7 @@ static LWPOLY* lwpoly_from_twkb_state(twkb_parse_state *s)
 */
 static LWCOLLECTION* lwmultipoint_from_twkb_state(twkb_parse_state *s)
 {
-	int ngeoms, i;
+	uint32_t ngeoms, i;
 	LWGEOM *geom = NULL;
 	LWCOLLECTION *col = lwcollection_construct_empty(s->lwtype, SRID_UNKNOWN, s->has_z, s->has_m);
 
@@ -345,9 +388,12 @@ static LWCOLLECTION* lwmultipoint_from_twkb_state(twkb_parse_state *s)
 		return col;
 
 	/* Read number of geometries */
-	ngeoms = twkb_parse_state_uvarint(s);
+	ngeoms = twkb_parse_state_uvarint32(s);
 	LWDEBUGF(4,"Number of geometries %d", ngeoms);
 
+	if (s->has_idlist && !twkb_parse_state_has_min_bytes(s, ngeoms, 1))
+		return col;
+
 	/* It has an idlist, we need to skip that */
 	if ( s->has_idlist )
 	{
@@ -355,6 +401,9 @@ static LWCOLLECTION* lwmultipoint_from_twkb_state(twkb_parse_state *s)
 			twkb_parse_state_varint_skip(s);
 	}
 
+	if (!twkb_parse_state_has_min_bytes(s, ngeoms, s->ndims))
+		return col;
+
 	for ( i = 0; i < ngeoms; i++ )
 	{
 		geom = lwpoint_as_lwgeom(lwpoint_from_twkb_state(s));
@@ -373,7 +422,7 @@ static LWCOLLECTION* lwmultipoint_from_twkb_state(twkb_parse_state *s)
 */
 static LWCOLLECTION* lwmultiline_from_twkb_state(twkb_parse_state *s)
 {
-	int ngeoms, i;
+	uint32_t ngeoms, i;
 	LWGEOM *geom = NULL;
 	LWCOLLECTION *col = lwcollection_construct_empty(s->lwtype, SRID_UNKNOWN, s->has_z, s->has_m);
 
@@ -383,10 +432,13 @@ static LWCOLLECTION* lwmultiline_from_twkb_state(twkb_parse_state *s)
 		return col;
 
 	/* Read number of geometries */
-	ngeoms = twkb_parse_state_uvarint(s);
+	ngeoms = twkb_parse_state_uvarint32(s);
 
 	LWDEBUGF(4,"Number of geometries %d",ngeoms);
 
+	if (s->has_idlist && !twkb_parse_state_has_min_bytes(s, ngeoms, 1))
+		return col;
+
 	/* It has an idlist, we need to skip that */
 	if ( s->has_idlist )
 	{
@@ -394,6 +446,9 @@ static LWCOLLECTION* lwmultiline_from_twkb_state(twkb_parse_state *s)
 			twkb_parse_state_varint_skip(s);
 	}
 
+	if (!twkb_parse_state_has_min_bytes(s, ngeoms, 1))
+		return col;
+
 	for ( i = 0; i < ngeoms; i++ )
 	{
 		geom = lwline_as_lwgeom(lwline_from_twkb_state(s));
@@ -412,7 +467,7 @@ static LWCOLLECTION* lwmultiline_from_twkb_state(twkb_parse_state *s)
 */
 static LWCOLLECTION* lwmultipoly_from_twkb_state(twkb_parse_state *s)
 {
-	int ngeoms, i;
+	uint32_t ngeoms, i;
 	LWGEOM *geom = NULL;
 	LWCOLLECTION *col = lwcollection_construct_empty(s->lwtype, SRID_UNKNOWN, s->has_z, s->has_m);
 
@@ -422,9 +477,12 @@ static LWCOLLECTION* lwmultipoly_from_twkb_state(twkb_parse_state *s)
 		return col;
 
 	/* Read number of geometries */
-	ngeoms = twkb_parse_state_uvarint(s);
+	ngeoms = twkb_parse_state_uvarint32(s);
 	LWDEBUGF(4,"Number of geometries %d",ngeoms);
 
+	if (s->has_idlist && !twkb_parse_state_has_min_bytes(s, ngeoms, 1))
+		return col;
+
 	/* It has an idlist, we need to skip that */
 	if ( s->has_idlist )
 	{
@@ -432,6 +490,9 @@ static LWCOLLECTION* lwmultipoly_from_twkb_state(twkb_parse_state *s)
 			twkb_parse_state_varint_skip(s);
 	}
 
+	if (!twkb_parse_state_has_min_bytes(s, ngeoms, 1))
+		return col;
+
 	for ( i = 0; i < ngeoms; i++ )
 	{
 		geom = lwpoly_as_lwgeom(lwpoly_from_twkb_state(s));
@@ -451,7 +512,7 @@ static LWCOLLECTION* lwmultipoly_from_twkb_state(twkb_parse_state *s)
 **/
 static LWCOLLECTION* lwcollection_from_twkb_state(twkb_parse_state *s)
 {
-	int ngeoms, i;
+	uint32_t ngeoms, i;
 	LWGEOM *geom = NULL;
 	LWCOLLECTION *col = lwcollection_construct_empty(s->lwtype, SRID_UNKNOWN, s->has_z, s->has_m);
 
@@ -461,10 +522,13 @@ static LWCOLLECTION* lwcollection_from_twkb_state(twkb_parse_state *s)
 		return col;
 
 	/* Read number of geometries */
-	ngeoms = twkb_parse_state_uvarint(s);
+	ngeoms = twkb_parse_state_uvarint32(s);
 
 	LWDEBUGF(4,"Number of geometries %d",ngeoms);
 
+	if (s->has_idlist && !twkb_parse_state_has_min_bytes(s, ngeoms, 1))
+		return col;
+
 	/* It has an idlist, we need to skip that */
 	if ( s->has_idlist )
 	{
@@ -472,6 +536,9 @@ static LWCOLLECTION* lwcollection_from_twkb_state(twkb_parse_state *s)
 			twkb_parse_state_varint_skip(s);
 	}
 
+	if (!twkb_parse_state_has_min_bytes(s, ngeoms, 2))
+		return col;
+
 	for ( i = 0; i < ngeoms; i++ )
 	{
 		geom = lwgeom_from_twkb_state(s);
diff --git a/liblwgeom/varint.c b/liblwgeom/varint.c
index fca005364..5e62579e7 100644
--- a/liblwgeom/varint.c
+++ b/liblwgeom/varint.c
@@ -20,10 +20,10 @@
  *
  * Copyright (C) 2014 Sandro Santilli <strk at kbt.io>
  * Copyright (C) 2013 Nicklas Avén
+ * Copyright (C) 2026 Darafei Praliaskouski <me at komzpa.net>
  *
  **********************************************************************/
 
-
 #include "varint.h"
 #include "lwgeom_log.h"
 #include "liblwgeom.h"
@@ -120,6 +120,12 @@ varint_u64_decode(const uint8_t *the_start, const uint8_t *the_end, size_t *size
 		/* Hibit is set, so this isn't the last byte */
 		if (nByte & 0x80)
 		{
+			if (nShift >= 63)
+			{
+				*size = 0;
+				lwerror("%s: varint exceeds 64 bits", __func__);
+				return 0;
+			}
 			/* We get here when there is more to read in the input varInt */
 			/* Here we take the least significant 7 bits of the read */
 			/* byte and put it in the most significant place in the result variable. */
@@ -131,6 +137,12 @@ varint_u64_decode(const uint8_t *the_start, const uint8_t *the_end, size_t *size
 		}
 		else
 		{
+			if (nShift >= 64 || (nShift == 63 && nByte > 1))
+			{
+				*size = 0;
+				lwerror("%s: varint exceeds 64 bits", __func__);
+				return 0;
+			}
 			/* move the "cursor" one step */
 			ptr++;
 			/* Move the last read byte to the most significant */
@@ -207,5 +219,3 @@ int8_t unzigzag8(uint8_t val)
 		((int8_t)(val >> 1)) :
 		(-1 * (int8_t)((val+1) >> 1));
 }
-
-

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

Summary of changes:
 GNUmakefile.in                                     |  13 ++
 NEWS                                               |   2 +
 fuzzers/Makefile                                   |  77 ++++++++-
 fuzzers/README.md                                  |  14 +-
 fuzzers/build_google_oss_fuzzers.sh                |  73 ++++++--
 fuzzers/build_oss_fuzz.sh                          |   8 +-
 fuzzers/install_oss_fuzz_build_deps.sh             |   4 +-
 fuzzers/liblwgeom_fuzzer.hpp                       | 164 ++++++++++++++++++
 fuzzers/raster_deserialize_fuzzer.c                | 185 +++++++++++++++++++++
 ...r.options => raster_deserialize_fuzzer.options} |   0
 fuzzers/raster_deserialize_fuzzer_seed_corpus.zip  | Bin 0 -> 586 bytes
 fuzzers/twkb_import_fuzzer.cpp                     |  56 +++++++
 ...t_fuzzer.options => twkb_import_fuzzer.options} |   0
 fuzzers/twkb_import_fuzzer_seed_corpus.zip         | Bin 0 -> 680 bytes
 fuzzers/wkb_import_fuzzer.cpp                      |  90 ++--------
 fuzzers/wkt_import_fuzzer.cpp                      |  98 +++--------
 liblwgeom/cunit/cu_in_twkb.c                       |  85 ++++++++++
 liblwgeom/lwin_twkb.c                              |  95 +++++++++--
 liblwgeom/varint.c                                 |  16 +-
 19 files changed, 785 insertions(+), 195 deletions(-)
 create mode 100644 fuzzers/liblwgeom_fuzzer.hpp
 create mode 100644 fuzzers/raster_deserialize_fuzzer.c
 copy fuzzers/{geojson_import_fuzzer.options => raster_deserialize_fuzzer.options} (100%)
 create mode 100644 fuzzers/raster_deserialize_fuzzer_seed_corpus.zip
 create mode 100644 fuzzers/twkb_import_fuzzer.cpp
 copy fuzzers/{geojson_import_fuzzer.options => twkb_import_fuzzer.options} (100%)
 create mode 100644 fuzzers/twkb_import_fuzzer_seed_corpus.zip


hooks/post-receive
-- 
PostGIS


More information about the postgis-tickets mailing list