From 1f5f3053cd03654f296ad96cfcc185fdc8fe9b10 Mon Sep 17 00:00:00 2001
From: Sami Imseih <simseih@amazon.com>
Date: Thu, 5 Mar 2026 16:13:12 -0600
Subject: [PATCH v2 1/1] Fix DSA pagemap undersizing in make_new_segment

When make_new_segment() creates an odd-sized segment, the pagemap is
only sized for usable_pages entries.  But the segment also contains
metadata pages, and the FreePageManager uses absolute page indices
that cover the entire segment.  Accesses to pagemap entries beyond
usable_pages are out of bounds.

The normal (geometric) path correctly sizes the pagemap for all pages
in the segment.  The odd-sized path should do the same, but it works
forward from usable_pages rather than backward from total_size.

Fix by adding pagemap entries for the metadata pages after the initial
metadata_bytes calculation.  Use integer ceiling division to compute
the exact number of additional entries needed, avoiding iteration.

Add an Assert to verify the pagemap covers all pages in the segment.

Author: Paul Bunn <paul.bunn@icloud.com>
Reported-by: Paul Bunn <paul.bunn@icloud.com>
---
 src/backend/utils/mmgr/dsa.c | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/src/backend/utils/mmgr/dsa.c b/src/backend/utils/mmgr/dsa.c
index ce9ede4c196..222fa86d83d 100644
--- a/src/backend/utils/mmgr/dsa.c
+++ b/src/backend/utils/mmgr/dsa.c
@@ -2196,6 +2196,8 @@ make_new_segment(dsa_area *area, size_t requested_pages)
 	/* See if that is enough... */
 	if (requested_pages > usable_pages)
 	{
+		size_t		total_pages;
+
 		/*
 		 * We'll make an odd-sized segment, working forward from the requested
 		 * number of pages.
@@ -2206,10 +2208,28 @@ make_new_segment(dsa_area *area, size_t requested_pages)
 			MAXALIGN(sizeof(FreePageManager)) +
 			usable_pages * sizeof(dsa_pointer);
 
+		/*
+		 * We must also account for pagemap entries needed to cover the
+		 * metadata pages themselves.  The pagemap must track all pages in the
+		 * segment, including the pages occupied by metadata.
+		 */
+		metadata_bytes +=
+			((metadata_bytes + (FPM_PAGE_SIZE - sizeof(dsa_pointer)) - 1) /
+			 (FPM_PAGE_SIZE - sizeof(dsa_pointer))) *
+			sizeof(dsa_pointer);
+
 		/* Add padding up to next page boundary. */
 		if (metadata_bytes % FPM_PAGE_SIZE != 0)
 			metadata_bytes += FPM_PAGE_SIZE - (metadata_bytes % FPM_PAGE_SIZE);
 		total_size = metadata_bytes + usable_pages * FPM_PAGE_SIZE;
+		total_pages = total_size / FPM_PAGE_SIZE;
+
+		/*
+		 * Verify we allocated enough pagemap entries for metadata and usable
+		 * pages
+		 */
+		Assert((metadata_bytes - MAXALIGN(sizeof(dsa_segment_header)) -
+				MAXALIGN(sizeof(FreePageManager))) / sizeof(dsa_pointer) >= total_pages);
 
 		/* Is that too large for dsa_pointer's addressing scheme? */
 		if (total_size > DSA_MAX_SEGMENT_SIZE)
-- 
2.50.1 (Apple Git-155)

