v20260406-0006-Add-support-to-protect-unused-resizable_sh.patch
text/x-patch
Filename: v20260406-0006-Add-support-to-protect-unused-resizable_sh.patch
Type: text/x-patch
Part: 5
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch v20260406-0006
Subject: Add support to protect unused resizable_shmem structure
| File | + | − |
|---|---|---|
| doc/src/sgml/xfunc.sgml | 10 | 3 |
| src/backend/port/sysv_shmem.c | 55 | 0 |
| src/backend/port/win32_shmem.c | 8 | 0 |
| src/backend/storage/ipc/shmem.c | 60 | 0 |
| src/include/storage/pg_shmem.h | 1 | 0 |
| src/include/storage/shmem.h | 1 | 0 |
| src/test/modules/resizable_shmem/resizable_shmem.c | 51 | 0 |
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