From ef321b327e7ff0a9abc1eb34f11910d7b71d32dd Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
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 <ashutosh.bapat.oss@gmail.com>
Suggested-by: Matthias van de Meent <boekewurm+postgres@gmail.com>
---
 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.
+    </para>
+
+    <para>
+    <function>ShmemProtectStruct</function> 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.
     </para>
 
     <para>
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

