From ef321b327e7ff0a9abc1eb34f11910d7b71d32dd Mon Sep 17 00:00:00 2001 From: Ashutosh Bapat Date: Mon, 6 Apr 2026 19:00:14 +0530 Subject: [PATCH v20260406 6/6] Add support to protect unused resizable_shmem structure Add APIs to make the portion of resizable_shmem structure beyond its current size inaccessible. Author: Ashutosh Bapat Suggested-by: Matthias van de Meent --- doc/src/sgml/xfunc.sgml | 13 +++- src/backend/port/sysv_shmem.c | 55 +++++++++++++++++ src/backend/port/win32_shmem.c | 8 +++ src/backend/storage/ipc/shmem.c | 60 +++++++++++++++++++ src/include/storage/pg_shmem.h | 1 + src/include/storage/shmem.h | 1 + .../modules/resizable_shmem/resizable_shmem.c | 51 ++++++++++++++++ 7 files changed, 186 insertions(+), 3 deletions(-) diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml index a6c7b8b1b22..62b33366f30 100644 --- a/doc/src/sgml/xfunc.sgml +++ b/doc/src/sgml/xfunc.sgml @@ -3781,9 +3781,16 @@ my_shmem_init(void *arg) shared structure. Also accessing the memory beyond the current size of the structure will not cause any segmentation fault or a bus error. Memory will be allocated during such a write access. 0s will be returned on such a read - access if memory is not allocated yet. The additional synchronization may - use mprotect() with PROT_NONE in every backend that may access this memory - to ensure that such an access results in a fault. + access if memory is not allocated yet. + + + + ShmemProtectStruct can be called when resizing the + structure to make the unused portion of the structure inaccessible and the + used portion accessible. These protections work only at the memory page + level, so some unused portion may still remain accessible. Please note that + the function modifies the protections only in the backend where it is run. + It needs to be called from every backend that may access the structure. diff --git a/src/backend/port/sysv_shmem.c b/src/backend/port/sysv_shmem.c index bb2a81417c6..14b6fa7f7e6 100644 --- a/src/backend/port/sysv_shmem.c +++ b/src/backend/port/sysv_shmem.c @@ -1065,8 +1065,63 @@ PGSharedMemoryEnsureAllocated(void *addr, Size size) Assert(addr == (void *) TYPEALIGN(GetOSPageSize(), addr)); Assert(size == TYPEALIGN(GetOSPageSize(), size)); + /* + * Ensure that MADV_POPULATE_WRITE can initialize the newly allocated + * pages. + */ + if (mprotect(addr, size, PROT_READ | PROT_WRITE) != 0) + ereport(ERROR, + (errmsg("could not protect shared memory: %m"))); + if (madvise(addr, size, MADV_POPULATE_WRITE) == -1) ereport(ERROR, (errmsg("could not allocate shared memory: %m"))); #endif } + +/* + * Set memory protection on the given region of shared memory. + * + * Makes [rw_start, rw_end) readable and writable, and [rw_end, prot_end) + * inaccessible. + * + * All addresses are expected to be page aligned. + * + * Only supported on platforms that support resizable shared memory. + */ +void +PGSharedMemoryProtect(void *rw_start, void *rw_end, void *prot_end) +{ +#ifndef HAVE_RESIZABLE_SHMEM + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("resizable shared memory is not supported on this platform"))); +#else + + if (!AnonymousShmem) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only anonymous shared memory can be protected at runtime"))); + + Assert(rw_start == (void *) TYPEALIGN(GetOSPageSize(), rw_start)); + Assert(rw_end == (void *) TYPEALIGN(GetOSPageSize(), rw_end)); + Assert(prot_end == (void *) TYPEALIGN(GetOSPageSize(), prot_end)); + Assert(rw_end >= rw_start); + + if (rw_end > rw_start) + { + if (mprotect(rw_start, (char *) rw_end - (char *) rw_start, + PROT_READ | PROT_WRITE) != 0) + ereport(ERROR, + (errmsg("could not protect shared memory: %m"))); + } + + if (prot_end > rw_end) + { + if (mprotect(rw_end, (char *) prot_end - (char *) rw_end, + PROT_NONE) != 0) + ereport(ERROR, + (errmsg("could not protect shared memory: %m"))); + } +#endif +} diff --git a/src/backend/port/win32_shmem.c b/src/backend/port/win32_shmem.c index c1f30665e66..b5396e4a5e8 100644 --- a/src/backend/port/win32_shmem.c +++ b/src/backend/port/win32_shmem.c @@ -693,3 +693,11 @@ PGSharedMemoryEnsureAllocated(void *addr, Size size) (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("resizable shared memory is not supported on this platform"))); } + +void +PGSharedMemoryProtect(void *rw_start, void *rw_end, void *prot_end) +{ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("resizable shared memory is not supported on this platform"))); +} diff --git a/src/backend/storage/ipc/shmem.c b/src/backend/storage/ipc/shmem.c index 115c543d36a..a3ed082e4d9 100644 --- a/src/backend/storage/ipc/shmem.c +++ b/src/backend/storage/ipc/shmem.c @@ -880,6 +880,66 @@ ShmemResizeStruct(const char *name, Size new_size) #endif } +/* + * ShmemProtectStruct() --- protect the unused portion of a resizable structure. + * + * Makes the region beyond the current size up to maximum_size inaccessible, and + * ensures the region up to the current size is readable and writable. Depending + * upon the platform, the protection honours the page boundaries. So it may be + * more permissible than strictly needed. + * + * Only works for resizable structures. Should be called in every backend that + * may access the resizable structure while resizing it. + */ +void +ShmemProtectStruct(const char *name) +{ +#ifndef HAVE_RESIZABLE_SHMEM + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("resizable shared memory is not supported on this platform"))); +#else + ShmemIndexEnt *result; + bool found; + Size page_size = GetOSPageSize(); + char *rw_start; + char *rw_end; + char *prot_end; + + LWLockAcquire(ShmemIndexLock, LW_SHARED); + result = (ShmemIndexEnt *) hash_search(ShmemIndex, name, HASH_FIND, &found); + if (!found) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("shmem struct \"%s\" is not initialized", name))); + + if (result->minimum_size == result->maximum_size) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("shared memory struct \"%s\" is not resizable", name))); + + /* Resizable structures are only supported with mmap-based shared memory. */ + Assert(shared_memory_type == SHMEM_TYPE_MMAP); + + /* Make at least [location, location+size) readable and writable */ + rw_start = (char *) TYPEALIGN_DOWN(page_size, result->location); + rw_end = (char *) TYPEALIGN(page_size, + (char *) result->location + result->size); + + /* + * Make remaining portion inaccessible while making sure that the portion + * after maximum_size is not affected since it may be used by other + * structures. + */ + prot_end = (char *) TYPEALIGN_DOWN(page_size, + (char *) result->location + result->maximum_size); + + LWLockRelease(ShmemIndexLock); + + PGSharedMemoryProtect(rw_start, rw_end, prot_end); +#endif +} + /* * InitShmemAllocator() --- set up basic pointers to shared memory. * diff --git a/src/include/storage/pg_shmem.h b/src/include/storage/pg_shmem.h index f0efbf2aec1..5165b815cc1 100644 --- a/src/include/storage/pg_shmem.h +++ b/src/include/storage/pg_shmem.h @@ -91,6 +91,7 @@ extern bool PGSharedMemoryIsInUse(unsigned long id1, unsigned long id2); extern void PGSharedMemoryDetach(void); extern void PGSharedMemoryEnsureFreed(void *addr, Size size); extern void PGSharedMemoryEnsureAllocated(void *addr, Size size); +extern void PGSharedMemoryProtect(void *rw_start, void *rw_end, void *prot_end); extern void GetHugePageSize(Size *hugepagesize, int *mmap_flags); extern Size GetOSPageSize(void); diff --git a/src/include/storage/shmem.h b/src/include/storage/shmem.h index 0e6d5a63f28..f8ddb0dd7c0 100644 --- a/src/include/storage/shmem.h +++ b/src/include/storage/shmem.h @@ -185,6 +185,7 @@ typedef struct ShmemCallbacks extern void RegisterShmemCallbacks(const ShmemCallbacks *callbacks); extern bool ShmemAddrIsValid(const void *addr); extern void ShmemResizeStruct(const char *name, Size new_size); +extern void ShmemProtectStruct(const char *name); /* * These macros provide syntactic sugar for calling the underlying functions diff --git a/src/test/modules/resizable_shmem/resizable_shmem.c b/src/test/modules/resizable_shmem/resizable_shmem.c index d035d767a62..c02ba54896a 100644 --- a/src/test/modules/resizable_shmem/resizable_shmem.c +++ b/src/test/modules/resizable_shmem/resizable_shmem.c @@ -56,10 +56,12 @@ static bool use_unknown_size = false; static void resizable_shmem_request(void *arg); static void resizable_shmem_shmem_init(void *arg); +static void resizable_shmem_shmem_attach(void *arg); static ShmemCallbacks shmem_callbacks = { .request_fn = resizable_shmem_request, .init_fn = resizable_shmem_shmem_init, + .attach_fn = resizable_shmem_shmem_attach, }; /* SQL-callable functions */ @@ -172,10 +174,32 @@ resizable_shmem_shmem_init(void *arg) */ Assert(resizable_shmem != NULL); +#ifdef HAVE_RESIZABLE_SHMEM + /* Protect the shared memory structure in this backend. */ + ShmemProtectStruct("resizable_shmem"); +#endif + resizable_shmem->num_entries = test_initial_entries; memset(resizable_shmem->data, 0, mul_size(test_initial_entries, TEST_ENTRY_SIZE)); } +/* + * Protect the shared memory structure memory after attaching. + */ +static void +resizable_shmem_shmem_attach(void *arg) +{ + /* + * Shared memory structure should have been already allocated. Initialize + * it. + */ + Assert(resizable_shmem != NULL); + +#ifdef HAVE_RESIZABLE_SHMEM + ShmemProtectStruct("resizable_shmem"); +#endif +} + /* * Resize the shared memory structure to accommodate the specified number of * entries. @@ -198,6 +222,7 @@ resizable_shmem_resize(PG_FUNCTION_ARGS) new_size = add_size(offsetof(TestResizableShmemStruct, data), mul_size(new_entries, TEST_ENTRY_SIZE)); ShmemResizeStruct("resizable_shmem", new_size); + ShmemProtectStruct("resizable_shmem"); resizable_shmem->num_entries = new_entries; PG_RETURN_VOID(); @@ -217,6 +242,19 @@ resizable_shmem_write(PG_FUNCTION_ARGS) (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("resizable_shmem is not initialized"))); +#ifdef HAVE_RESIZABLE_SHMEM + + /* + * Ideally the structure should be protected through a synchronization + * cycle across all the backends that may access the structure. But we + * don't implement any such synchronization in this test module to keep it + * simple. Given that ProcSignalBarrier mechanism is not extensible, we + * may not be able to do that as well here. Hence add protect just before + * accessing the structure. + */ + ShmemProtectStruct("resizable_shmem"); +#endif + /* Write the value to all current entries */ for (i = 0; i < resizable_shmem->num_entries; i++) resizable_shmem->data[i] = entry_value; @@ -245,6 +283,19 @@ resizable_shmem_read(PG_FUNCTION_ARGS) (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("entry_count %d is out of range (0..%d)", entry_count, resizable_shmem->num_entries))); +#ifdef HAVE_RESIZABLE_SHMEM + + /* + * Ideally the structure should be protected through a synchronization + * cycle across all the backends that may access the structure. But we + * don't implement any such synchronization in this test module to keep it + * simple. Given that ProcSignalBarrier mechanism is not extensible, we + * may not be able to do that as well here. Hence add protect just before + * accessing the structure. + */ + ShmemProtectStruct("resizable_shmem"); +#endif + for (i = 0; i < entry_count; i++) { if (resizable_shmem->data[i] != entry_value) -- 2.34.1