From b4fd2ea31b670b9beac51b275cabc91e465eebe7 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 v20260407 2/3] 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                  |  4 +-
 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 +
 src/test/modules/test_shmem/test_shmem.c | 43 +++++++++++++++++
 7 files changed, 171 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 22f953db9d7..04b312fcf94 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -3780,7 +3780,9 @@ my_shmem_init(void *arg)
     additional synchronization between the resizing process and the processes
     using the shared structure. Also it needs to implement additional protection
     to prevent access to the part of the address space beyond the size of the
-    structure when resizing it.
+    structure when resizing it. <function>ShmemProtectStruct</function> can be
+    called from every backend that may access the resizable structure for the
+    same.
     </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 8f006967790..61808c7a8e5 100644
--- a/src/backend/storage/ipc/shmem.c
+++ b/src/backend/storage/ipc/shmem.c
@@ -875,6 +875,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/test_shmem/test_shmem.c b/src/test/modules/test_shmem/test_shmem.c
index 72004df4083..e95225bf40a 100644
--- a/src/test/modules/test_shmem/test_shmem.c
+++ b/src/test/modules/test_shmem/test_shmem.c
@@ -133,6 +133,12 @@ test_shmem_init(void *arg)
 	/* Resizable structure should have been already allocated. Initialize it. */
 	Assert(ResizableShmem != NULL);
 
+
+#ifdef HAVE_RESIZABLE_SHMEM
+	/* Protect the shared memory structure in this backend. */
+	ShmemProtectStruct("resizable_shmem");
+#endif
+
 	ResizableShmem->num_entries = initial_entries;
 	memset(ResizableShmem->data, 0, mul_size(initial_entries, TEST_ENTRY_SIZE));
 }
@@ -148,6 +154,16 @@ test_shmem_attach(void *arg)
 	if (attached_or_initialized)
 		elog(ERROR, "attach or initialize already called in this process");
 	attached_or_initialized = true;
+
+	/*
+	 * Shared memory structure should have been already allocated. Initialize
+	 * it.
+	 */
+	Assert(ResizableShmem != NULL);
+
+#ifdef HAVE_RESIZABLE_SHMEM
+	ShmemProtectStruct("resizable_shmem");
+#endif
 }
 
 void
@@ -259,6 +275,7 @@ resizable_shmem_resize(PG_FUNCTION_ARGS)
 	new_size = add_size(offsetof(TestResizableData, data),
 						mul_size(new_entries, TEST_ENTRY_SIZE));
 	ShmemResizeStruct("resizable_shmem", new_size);
+	ShmemProtectStruct("resizable_shmem");
 	ResizableShmem->num_entries = new_entries;
 
 	PG_RETURN_VOID();
@@ -280,6 +297,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 < ResizableShmem->num_entries; i++)
 		ResizableShmem->data[i] = entry_value;
@@ -309,6 +339,19 @@ resizable_shmem_read(PG_FUNCTION_ARGS)
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("entry_count %d is out of range (0..%d)", entry_count, ResizableShmem->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 (ResizableShmem->data[i] != entry_value)
-- 
2.34.1

