From d5393e7ecb9852c02d5b0e591fc5347e5fe9e0d7 Mon Sep 17 00:00:00 2001 From: Sami Imseih Date: Thu, 5 Mar 2026 16:13:12 -0600 Subject: [PATCH v4 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. Regression tests added to ensure dsa_allocate succeeds for a wide range of allocations. Author: Paul Bunn Reported-by: Paul Bunn --- src/backend/utils/mmgr/dsa.c | 20 +++++++++++ .../modules/test_dsa/expected/test_dsa.out | 10 ++++++ src/test/modules/test_dsa/sql/test_dsa.sql | 5 +++ src/test/modules/test_dsa/test_dsa--1.0.sql | 4 +++ src/test/modules/test_dsa/test_dsa.c | 36 +++++++++++++++++++ 5 files changed, 75 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) diff --git a/src/test/modules/test_dsa/expected/test_dsa.out b/src/test/modules/test_dsa/expected/test_dsa.out index 266010e77fe..2fd10fb349c 100644 --- a/src/test/modules/test_dsa/expected/test_dsa.out +++ b/src/test/modules/test_dsa/expected/test_dsa.out @@ -11,3 +11,13 @@ SELECT test_dsa_resowners(); (1 row) +-- +-- Test up to 10000 pages skipping 100 pages to cover a wide range of +-- segment layouts without making the test too slow. +-- +SELECT test_dsa_allocate(10000, 100); + test_dsa_allocate +------------------- + +(1 row) + diff --git a/src/test/modules/test_dsa/sql/test_dsa.sql b/src/test/modules/test_dsa/sql/test_dsa.sql index c3d8db94372..6b10fe14ba6 100644 --- a/src/test/modules/test_dsa/sql/test_dsa.sql +++ b/src/test/modules/test_dsa/sql/test_dsa.sql @@ -2,3 +2,8 @@ CREATE EXTENSION test_dsa; SELECT test_dsa_basic(); SELECT test_dsa_resowners(); +-- +-- Test up to 10000 pages skipping 100 pages to cover a wide range of +-- segment layouts without making the test too slow. +-- +SELECT test_dsa_allocate(10000, 100); \ No newline at end of file diff --git a/src/test/modules/test_dsa/test_dsa--1.0.sql b/src/test/modules/test_dsa/test_dsa--1.0.sql index 2904cb23525..70baf27123a 100644 --- a/src/test/modules/test_dsa/test_dsa--1.0.sql +++ b/src/test/modules/test_dsa/test_dsa--1.0.sql @@ -10,3 +10,7 @@ CREATE FUNCTION test_dsa_basic() CREATE FUNCTION test_dsa_resowners() RETURNS pg_catalog.void AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION test_dsa_allocate(int, int) + RETURNS pg_catalog.void + AS 'MODULE_PATHNAME' LANGUAGE C; diff --git a/src/test/modules/test_dsa/test_dsa.c b/src/test/modules/test_dsa/test_dsa.c index ed2a07c962f..a72871ea0d3 100644 --- a/src/test/modules/test_dsa/test_dsa.c +++ b/src/test/modules/test_dsa/test_dsa.c @@ -16,6 +16,7 @@ #include "storage/dsm_registry.h" #include "storage/lwlock.h" #include "utils/dsa.h" +#include "utils/freepage.h" #include "utils/resowner.h" PG_MODULE_MAGIC; @@ -120,3 +121,38 @@ test_dsa_resowners(PG_FUNCTION_ARGS) PG_RETURN_VOID(); } + +/* + * test_dsa_allocate + * + * Test DSA allocation across a range of sizes to exercise the pagemap + * sizing logic in make_new_segment(). A fresh DSA is created for each + * iteration so that each allocation triggers a new segment creation, + * including the odd-sized segment path. + */ +PG_FUNCTION_INFO_V1(test_dsa_allocate); +Datum +test_dsa_allocate(PG_FUNCTION_ARGS) +{ + int num_pages = PG_GETARG_INT32(0); + int step = PG_GETARG_INT32(1); + size_t usable_pages; + int *tranche_id; + bool found; + dsa_area *a; + dsa_pointer dp; + + tranche_id = GetNamedDSMSegment("test_dsa", sizeof(int), + init_tranche, &found, NULL); + + for (usable_pages = 1; usable_pages < num_pages; usable_pages += step) + { + a = dsa_create(*tranche_id); + dp = dsa_allocate(a, usable_pages * FPM_PAGE_SIZE); + + dsa_free(a, dp); + dsa_detach(a); + } + + PG_RETURN_VOID(); +} -- 2.50.1 (Apple Git-155)