From 2226c23bfe1c7a3553bb34b1ce953130101e332a Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Sat, 4 Jul 2026 13:17:15 +0200 Subject: [PATCH v3 3/3] Toy extension to make it easier to test out of bounds access I built this extension just for my personal use while developing so I can trigger out of bounds array access by accessing an array index outside of a 10 element array in shared memory. It can be used to test both the ShmemAllocator and shm_toc, including access from a background worker --- contrib/meson.build | 1 + contrib/valgrind_shmem/meson.build | 18 ++ .../valgrind_shmem/valgrind_shmem--1.0.sql | 16 ++ contrib/valgrind_shmem/valgrind_shmem.c | 227 ++++++++++++++++++ contrib/valgrind_shmem/valgrind_shmem.control | 5 + 5 files changed, 267 insertions(+) create mode 100644 contrib/valgrind_shmem/meson.build create mode 100644 contrib/valgrind_shmem/valgrind_shmem--1.0.sql create mode 100644 contrib/valgrind_shmem/valgrind_shmem.c create mode 100644 contrib/valgrind_shmem/valgrind_shmem.control diff --git a/contrib/meson.build b/contrib/meson.build index ebb7f83d8c5..3159c4084b0 100644 --- a/contrib/meson.build +++ b/contrib/meson.build @@ -72,4 +72,5 @@ subdir('tsm_system_time') subdir('unaccent') subdir('uuid-ossp') subdir('vacuumlo') +subdir('valgrind_shmem') subdir('xml2') diff --git a/contrib/valgrind_shmem/meson.build b/contrib/valgrind_shmem/meson.build new file mode 100644 index 00000000000..5b6206ed0cb --- /dev/null +++ b/contrib/valgrind_shmem/meson.build @@ -0,0 +1,18 @@ +# Copyright (c) 2022-2026, PostgreSQL Global Development Group + +valgrind_shmem_sources = files( + 'valgrind_shmem.c', +) + +valgrind_shmem = shared_module('valgrind_shmem', + valgrind_shmem_sources, + c_pch: pch_postgres_h, + kwargs: contrib_mod_args, +) +contrib_targets += valgrind_shmem + +install_data( + 'valgrind_shmem--1.0.sql', + 'valgrind_shmem.control', + kwargs: contrib_data_args, +) diff --git a/contrib/valgrind_shmem/valgrind_shmem--1.0.sql b/contrib/valgrind_shmem/valgrind_shmem--1.0.sql new file mode 100644 index 00000000000..234be9b1b07 --- /dev/null +++ b/contrib/valgrind_shmem/valgrind_shmem--1.0.sql @@ -0,0 +1,16 @@ +/* contrib/valgrind_shmem/valgrind_shmem--1.1.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION valgrind_shmem" to load this file. \quit + +CREATE FUNCTION valgrind_shmem_main_get(int) RETURNS int +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION valgrind_shmem_shm_toc_fg_get(int) RETURNS int +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION valgrind_shmem_shm_toc_bg_get(int) RETURNS int +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; diff --git a/contrib/valgrind_shmem/valgrind_shmem.c b/contrib/valgrind_shmem/valgrind_shmem.c new file mode 100644 index 00000000000..62d783362bc --- /dev/null +++ b/contrib/valgrind_shmem/valgrind_shmem.c @@ -0,0 +1,227 @@ +#include "postgres.h" + +#include "fmgr.h" +#include "miscadmin.h" +#include "postmaster/bgworker.h" +#include "storage/dsm.h" +#include "storage/ipc.h" +#include "storage/proc.h" +#include "storage/shm_toc.h" +#include "storage/shmem.h" +#include "utils/wait_event.h" + +PG_MODULE_MAGIC_EXT( + .name = "valgrind_shmem", + .version = PG_VERSION +); + +#define PG_TEST_VALGRIND_SHMEM_MAGIC 0x74686f72 + +PG_FUNCTION_INFO_V1(valgrind_shmem_main_get); +PG_FUNCTION_INFO_V1(valgrind_shmem_shm_toc_fg_get); +PG_FUNCTION_INFO_V1(valgrind_shmem_shm_toc_bg_get); + +pg_noreturn extern PGDLLEXPORT void valgrind_shmem_main(Datum main_arg); + +typedef struct +{ + int32 index; + bool done; + int32 result; +} WorkerHeader; + +typedef struct +{ + int32 a[10]; +} ShmemState; + +static ShmemState *state; +static int we_valdgrind_shmem_request = 0; + +static void +shmem_request(void *arg) +{ + ShmemRequestStruct(.name = "valgrind_shmem: state", + .size = sizeof(ShmemState), + .ptr = (void **) &state, + ); +} + +static void +shmem_init(void *arg) +{ + for (int i = 0; i < 10; i++) + state->a[i] = i * 2; +} + +static const ShmemCallbacks shmem_callbacks = { + .request_fn = shmem_request, + .init_fn = shmem_init, +}; + +/* + * Gets an array element from a struct in the main shared memory. + */ +Datum +valgrind_shmem_main_get(PG_FUNCTION_ARGS) +{ + int32 index = PG_GETARG_INT32(0); + + PG_RETURN_INT32(state->a[index]); +} + +/* + * Gets an array element from a struct in a shm_toc entry in DSM. + */ +Datum +valgrind_shmem_shm_toc_fg_get(PG_FUNCTION_ARGS) +{ + int32 index = PG_GETARG_INT32(0); + shm_toc_estimator e; + Size segsize; + dsm_segment *seg; + shm_toc *toc; + ShmemState *state; + int32 ret; + + shm_toc_initialize_estimator(&e); + shm_toc_estimate_chunk(&e, sizeof(ShmemState)); + shm_toc_estimate_keys(&e, 1); + segsize = shm_toc_estimate(&e); + + seg = dsm_create(shm_toc_estimate(&e), 0); + toc = shm_toc_create(PG_TEST_VALGRIND_SHMEM_MAGIC, dsm_segment_address(seg), segsize); + + state = shm_toc_allocate(toc, sizeof(ShmemState)); + for (int i = 0; i < 10; i++) + state->a[i] = i * 3; + shm_toc_insert(toc, 1, state); + + ret = state->a[index]; + + dsm_detach(seg); + + PG_RETURN_INT32(ret); +} + +/* + * Gets an array element from a struct in a shm_toc entry in DSM in a + * background worker. + */ +Datum +valgrind_shmem_shm_toc_bg_get(PG_FUNCTION_ARGS) +{ + int32 index = PG_GETARG_INT32(0); + shm_toc_estimator e; + Size segsize; + dsm_segment *seg; + shm_toc *toc; + WorkerHeader *header; + ShmemState *state; + BackgroundWorker worker = { + .bgw_flags = BGWORKER_SHMEM_ACCESS, + .bgw_start_time = BgWorkerStart_ConsistentState, + .bgw_restart_time = BGW_NEVER_RESTART, + .bgw_library_name = "valgrind_shmem", + .bgw_function_name = "valgrind_shmem_main", + .bgw_type = "valgrind_shmem", + .bgw_name = "valgrind_shmem worker", + .bgw_notify_pid = MyProcPid + }; + BackgroundWorkerHandle *whandle; + int32 ret; + + shm_toc_initialize_estimator(&e); + shm_toc_estimate_chunk(&e, sizeof(WorkerHeader)); + shm_toc_estimate_chunk(&e, sizeof(ShmemState)); + shm_toc_estimate_keys(&e, 2); + segsize = shm_toc_estimate(&e); + + seg = dsm_create(shm_toc_estimate(&e), 0); + toc = shm_toc_create(PG_TEST_VALGRIND_SHMEM_MAGIC, dsm_segment_address(seg), segsize); + + header = shm_toc_allocate(toc, sizeof(WorkerHeader)); + header->index = index; + header->done = false; + shm_toc_insert(toc, 0, header); + + state = shm_toc_allocate(toc, sizeof(ShmemState)); + for (int i = 0; i < 10; i++) + state->a[i] = i * 3; + shm_toc_insert(toc, 1, state); + + worker.bgw_main_arg = UInt32GetDatum(dsm_segment_handle(seg)); + + if (!RegisterDynamicBackgroundWorker(&worker, &whandle)) + ereport(ERROR, + errcode(ERRCODE_INSUFFICIENT_RESOURCES), + errmsg("could not register background process"), + errhint("You may need to increase \"max_worker_processes\".")); + + for (;;) + { + BgwHandleStatus status; + pid_t pid; + + if (header->done) + break; + + status = GetBackgroundWorkerPid(whandle, &pid); + if (status == BGWH_STOPPED || status == BGWH_POSTMASTER_DIED) + ereport(ERROR, + errmsg("background worker died unexpectedly")); + + if (we_valdgrind_shmem_request == 0) + we_valdgrind_shmem_request = WaitEventExtensionNew("TestValgrindHsmemRequest"); + + (void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0, + we_valdgrind_shmem_request); + + ResetLatch(MyLatch); + + CHECK_FOR_INTERRUPTS(); + } + + ret = header->result; + + dsm_detach(seg); + + PG_RETURN_INT32(ret); +} + +void +valgrind_shmem_main(Datum main_arg) +{ + dsm_segment *seg; + shm_toc *toc; + WorkerHeader *header; + ShmemState *state; + + seg = dsm_attach(DatumGetUInt32(main_arg)); + if (seg == NULL) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("unable to map dynamic shared memory segment"))); + + toc = shm_toc_attach(PG_TEST_VALGRIND_SHMEM_MAGIC, dsm_segment_address(seg)); + if (toc == NULL) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("bad magic number in dynamic shared memory segment"))); + + header = shm_toc_lookup(toc, 0, false); + state = shm_toc_lookup(toc, 1, false); + + pg_usleep(500 * 1000); + + header->result = state->a[header->index]; + header->done = true; + + proc_exit(0); +} + +void +_PG_init(void) +{ + RegisterShmemCallbacks(&shmem_callbacks); +} diff --git a/contrib/valgrind_shmem/valgrind_shmem.control b/contrib/valgrind_shmem/valgrind_shmem.control new file mode 100644 index 00000000000..b70bd0e2d10 --- /dev/null +++ b/contrib/valgrind_shmem/valgrind_shmem.control @@ -0,0 +1,5 @@ +# valigrind_shmem extension +comment = 'Dummy extension testing valgrind and shared memory' +default_version = '1.0' +module_pathname = '$libdir/valgrind_shmem' +relocatable = true -- 2.54.0