From 82942e629508a2f78b19e960e2d8ffaa55152cb1 Mon Sep 17 00:00:00 2001
From: Sehrope Sarkuni <sehrope@jackdb.com>
Date: Sun, 27 Sep 2026 12:48:04 +0000
Subject: [PATCH v1 2/7] gin: check each posting list segment fits before
 decoding it

ginPostingListDecodeAllSegments() set endptr from segment->nbytes and stepped
to the next segment with GinNextPostingListSegment() without checking either
against the end of the posting list, so a corrupt page could be read past its
end.  Check that each segment header and the nbytes it claims lie within the
remaining length first, comparing sizes so no out-of-bounds pointer is formed.
---
 src/backend/access/gin/ginpostinglist.c | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/src/backend/access/gin/ginpostinglist.c b/src/backend/access/gin/ginpostinglist.c
index 89da084bbe8..f7dec6318c9 100644
--- a/src/backend/access/gin/ginpostinglist.c
+++ b/src/backend/access/gin/ginpostinglist.c
@@ -287,6 +287,19 @@ ginPostingListDecodeAllSegments(GinPostingList *segment, int len, int *ndecoded_
 	ndecoded = 0;
 	while ((char *) segment < endseg)
 	{
+		/*
+		 * Reject a segment that runs past the end of the posting list.
+		 * Compare sizes rather than forming segment +
+		 * SizeOfGinPostingList(segment), which would be an out-of-bounds
+		 * pointer.  The header is tested before nbytes is read, and the loop
+		 * condition keeps endseg - segment positive for the unsigned cast.
+		 */
+		if (offsetof(GinPostingList, bytes) > (Size) (endseg - (char *) segment) ||
+			SizeOfGinPostingList(segment) > (Size) (endseg - (char *) segment))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATA_CORRUPTED),
+					 errmsg("corrupted GIN posting list")));
+
 		/* enlarge output array if needed */
 		if (ndecoded >= nallocated)
 		{
-- 
2.17.1

