agora inbox for [email protected]
help / color / mirror / Atom feed[PATCH v4] Avoid orphaned objects dependencies
250+ messages / 3 participants
[nested] [flat]
* [PATCH v4] Avoid orphaned objects dependencies
@ 2024-03-29 15:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2024-03-29 15:43 UTC (permalink / raw)
It's currently possible to create orphaned objects dependencies, for example:
Scenario 1:
session 1: begin; drop schema schem;
session 2: create a function in the schema schem
session 1: commit;
With the above, the function created in session 2 would be linked to a non
existing schema.
Scenario 2:
session 1: begin; create a function in the schema schem
session 2: drop schema schem;
session 1: commit;
With the above, the function created in session 1 would be linked to a non
existing schema.
To avoid those scenarios, a new lock (that conflicts with a lock taken by DROP)
has been put in place when the dependencies are being recorded. With this in
place, the drop schema in scenario 2 would be locked.
Also, after the new lock attempt, the patch checks that the object still exists:
with this in place session 2 in scenario 1 would be locked and would report an
error once session 1 committs (that would not be the case should session 1 abort
the transaction).
If the object is dropped before the new lock attempt is triggered then the patch
would also report an error (but with less details).
The patch adds a few tests for some dependency cases (that would currently produce
orphaned objects):
- schema and function (as the above scenarios)
- function and arg type
- function and return type
- function and function
- domain and domain
- table and type
- server and foreign data wrapper
---
src/backend/catalog/dependency.c | 54 +++++++++
src/backend/catalog/objectaddress.c | 70 +++++++++++
src/backend/catalog/pg_depend.c | 6 +
src/include/catalog/dependency.h | 1 +
src/include/catalog/objectaddress.h | 1 +
src/test/modules/Makefile | 1 +
src/test/modules/meson.build | 1 +
.../test_dependencies_locks/.gitignore | 3 +
.../modules/test_dependencies_locks/Makefile | 14 +++
.../expected/test_dependencies_locks.out | 113 ++++++++++++++++++
.../test_dependencies_locks/meson.build | 12 ++
.../specs/test_dependencies_locks.spec | 78 ++++++++++++
12 files changed, 354 insertions(+)
24.6% src/backend/catalog/
42.7% src/test/modules/test_dependencies_locks/expected/
25.9% src/test/modules/test_dependencies_locks/specs/
5.0% src/test/modules/test_dependencies_locks/
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index d4b5b2ade1..a49357bbe2 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -1517,6 +1517,60 @@ AcquireDeletionLock(const ObjectAddress *object, int flags)
}
}
+/*
+ * depLockAndCheckObject
+ *
+ * Lock the object that we are about to record a dependency on.
+ * After it's locked, verify that it hasn't been dropped while we
+ * weren't looking. If the object has been dropped, this function
+ * does not return!
+ */
+void
+depLockAndCheckObject(const ObjectAddress *object)
+{
+ char *object_description;
+
+ /*
+ * Those don't rely on LockDatabaseObject() when being dropped (see
+ * AcquireDeletionLock()). Also it looks like they can not produce
+ * orphaned dependent objects when being dropped.
+ */
+ if (object->classId == RelationRelationId || object->classId == AuthMemRelationId)
+ return;
+
+ object_description = getObjectDescription(object, true);
+
+ /* assume we should lock the whole object not a sub-object */
+ LockDatabaseObject(object->classId, object->objectId, 0, AccessShareLock);
+
+ /* check if object still exists */
+ if (!ObjectByIdExist(object, false))
+ {
+ /*
+ * It might be possible that we are creating it (for example creating
+ * a composite type while creating a relation), so bypass the syscache
+ * lookup and use a dirty snaphot instead to cover this scenario.
+ */
+ if (!ObjectByIdExist(object, true))
+ {
+ /*
+ * If the object has been dropped before we get a chance to get
+ * its description, then emit a generic error message. That looks
+ * like a good compromise over extra complexity.
+ */
+ if (object_description)
+ ereport(ERROR, errmsg("%s does not exist", object_description));
+ else
+ ereport(ERROR, errmsg("a dependent object does not exist"));
+ }
+ }
+
+ if (object_description)
+ pfree(object_description);
+
+ return;
+}
+
/*
* ReleaseDeletionLock - release an object deletion lock
*
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 7b536ac6fd..c2b873dd81 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -2590,6 +2590,76 @@ get_object_namespace(const ObjectAddress *address)
return oid;
}
+/*
+ * ObjectByIdExist
+ *
+ * Return whether the given object exists.
+ *
+ * Works for most catalogs, if no special processing is needed.
+ */
+bool
+ObjectByIdExist(const ObjectAddress *address, bool use_dirty_snapshot)
+{
+ HeapTuple tuple;
+ int cache = -1;
+ const ObjectPropertyType *property;
+
+ if (!use_dirty_snapshot)
+ {
+ property = get_object_property_data(address->classId);
+
+ cache = property->oid_catcache_id;
+ }
+
+ if (cache >= 0)
+ {
+ /* Fetch tuple from syscache. */
+ tuple = SearchSysCache1(cache, ObjectIdGetDatum(address->objectId));
+
+ if (!HeapTupleIsValid(tuple))
+ {
+ return false;
+ }
+
+ ReleaseSysCache(tuple);
+
+ return true;
+ }
+ else
+ {
+ Relation rel;
+ ScanKeyData skey[1];
+ SysScanDesc scan;
+ SnapshotData DirtySnapshot;
+ Snapshot snapshot;
+
+ if (use_dirty_snapshot)
+ {
+ InitDirtySnapshot(DirtySnapshot);
+ snapshot = &DirtySnapshot;
+ }
+ else
+ snapshot = NULL;
+
+ rel = table_open(address->classId, AccessShareLock);
+
+ ScanKeyInit(&skey[0],
+ get_object_attnum_oid(address->classId),
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(address->objectId));
+
+ scan = systable_beginscan(rel, get_object_oid_index(address->classId), true,
+ snapshot, 1, skey);
+
+ /* we expect exactly one match */
+ tuple = systable_getnext(scan);
+ systable_endscan(scan);
+ table_close(rel, AccessShareLock);
+
+ return (HeapTupleIsValid(tuple));
+ }
+}
+
/*
* Return ObjectType for the given object type as given by
* getObjectTypeDescription; if no valid ObjectType code exists, but it's a
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index f85a898de8..6bb218f5dd 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -106,6 +106,12 @@ recordMultipleDependencies(const ObjectAddress *depender,
if (isObjectPinned(referenced))
continue;
+ /*
+ * Acquire a lock and check object still exists while recording the
+ * dependency. XXX - Should we do so only for DEPENDENCY_NORMAL?
+ */
+ depLockAndCheckObject(referenced);
+
if (slot_init_count < max_slots)
{
slot[slot_stored_count] = MakeSingleTupleTableSlot(RelationGetDescr(dependDesc),
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index ec654010d4..8915548711 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -94,6 +94,7 @@ typedef struct ObjectAddresses ObjectAddresses;
/* in dependency.c */
extern void AcquireDeletionLock(const ObjectAddress *object, int flags);
+extern void depLockAndCheckObject(const ObjectAddress *object);
extern void ReleaseDeletionLock(const ObjectAddress *object);
diff --git a/src/include/catalog/objectaddress.h b/src/include/catalog/objectaddress.h
index 3a70d80e32..04891abcc1 100644
--- a/src/include/catalog/objectaddress.h
+++ b/src/include/catalog/objectaddress.h
@@ -53,6 +53,7 @@ extern void check_object_ownership(Oid roleid,
Node *object, Relation relation);
extern Oid get_object_namespace(const ObjectAddress *address);
+extern bool ObjectByIdExist(const ObjectAddress *address, bool use_dirty_snapshot);
extern bool is_objectclass_supported(Oid class_id);
extern const char *get_object_class_descr(Oid class_id);
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 256799f520..75f357100f 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -17,6 +17,7 @@ SUBDIRS = \
test_copy_callbacks \
test_custom_rmgrs \
test_ddl_deparse \
+ test_dependencies_locks \
test_dsa \
test_dsm_registry \
test_extensions \
diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build
index d8fe059d23..60305dcccd 100644
--- a/src/test/modules/meson.build
+++ b/src/test/modules/meson.build
@@ -16,6 +16,7 @@ subdir('test_bloomfilter')
subdir('test_copy_callbacks')
subdir('test_custom_rmgrs')
subdir('test_ddl_deparse')
+subdir('test_dependencies_locks')
subdir('test_dsa')
subdir('test_dsm_registry')
subdir('test_extensions')
diff --git a/src/test/modules/test_dependencies_locks/.gitignore b/src/test/modules/test_dependencies_locks/.gitignore
new file mode 100644
index 0000000000..bf000faac4
--- /dev/null
+++ b/src/test/modules/test_dependencies_locks/.gitignore
@@ -0,0 +1,3 @@
+# Generated subdirectories
+/log/
+/output_iso
diff --git a/src/test/modules/test_dependencies_locks/Makefile b/src/test/modules/test_dependencies_locks/Makefile
new file mode 100644
index 0000000000..7491048380
--- /dev/null
+++ b/src/test/modules/test_dependencies_locks/Makefile
@@ -0,0 +1,14 @@
+# src/test/modules/test_dependencies_locks/Makefile
+
+ISOLATION = test_dependencies_locks
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_dependencies_locks
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_dependencies_locks/expected/test_dependencies_locks.out b/src/test/modules/test_dependencies_locks/expected/test_dependencies_locks.out
new file mode 100644
index 0000000000..93318c9cbb
--- /dev/null
+++ b/src/test/modules/test_dependencies_locks/expected/test_dependencies_locks.out
@@ -0,0 +1,113 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1_begin s1_create_function_in_schema s2_drop_schema s1_commit
+step s1_begin: BEGIN;
+step s1_create_function_in_schema: CREATE FUNCTION testschema.foo() RETURNS int AS 'select 1' LANGUAGE sql;
+step s2_drop_schema: DROP SCHEMA testschema; <waiting ...>
+step s1_commit: COMMIT;
+step s2_drop_schema: <... completed>
+ERROR: cannot drop schema testschema because other objects depend on it
+
+starting permutation: s2_begin s2_drop_schema s1_create_function_in_schema s2_commit
+step s2_begin: BEGIN;
+step s2_drop_schema: DROP SCHEMA testschema;
+step s1_create_function_in_schema: CREATE FUNCTION testschema.foo() RETURNS int AS 'select 1' LANGUAGE sql; <waiting ...>
+step s2_commit: COMMIT;
+step s1_create_function_in_schema: <... completed>
+ERROR: schema testschema does not exist
+
+starting permutation: s1_begin s1_create_function_with_argtype s2_drop_foo_type s1_commit
+step s1_begin: BEGIN;
+step s1_create_function_with_argtype: CREATE FUNCTION fooargtype(num foo) RETURNS int AS 'select 1' LANGUAGE sql;
+step s2_drop_foo_type: DROP TYPE public.foo; <waiting ...>
+step s1_commit: COMMIT;
+step s2_drop_foo_type: <... completed>
+ERROR: cannot drop type foo because other objects depend on it
+
+starting permutation: s2_begin s2_drop_foo_type s1_create_function_with_argtype s2_commit
+step s2_begin: BEGIN;
+step s2_drop_foo_type: DROP TYPE public.foo;
+step s1_create_function_with_argtype: CREATE FUNCTION fooargtype(num foo) RETURNS int AS 'select 1' LANGUAGE sql; <waiting ...>
+step s2_commit: COMMIT;
+step s1_create_function_with_argtype: <... completed>
+ERROR: type foo does not exist
+
+starting permutation: s1_begin s1_create_function_with_rettype s2_drop_foo_rettype s1_commit
+step s1_begin: BEGIN;
+step s1_create_function_with_rettype: CREATE FUNCTION footrettype() RETURNS id LANGUAGE sql RETURN 1;
+step s2_drop_foo_rettype: DROP DOMAIN id; <waiting ...>
+step s1_commit: COMMIT;
+step s2_drop_foo_rettype: <... completed>
+ERROR: cannot drop type id because other objects depend on it
+
+starting permutation: s2_begin s2_drop_foo_rettype s1_create_function_with_rettype s2_commit
+step s2_begin: BEGIN;
+step s2_drop_foo_rettype: DROP DOMAIN id;
+step s1_create_function_with_rettype: CREATE FUNCTION footrettype() RETURNS id LANGUAGE sql RETURN 1; <waiting ...>
+step s2_commit: COMMIT;
+step s1_create_function_with_rettype: <... completed>
+ERROR: type id does not exist
+
+starting permutation: s1_begin s1_create_function_with_function s2_drop_function_f s1_commit
+step s1_begin: BEGIN;
+step s1_create_function_with_function: CREATE FUNCTION foofunc() RETURNS int LANGUAGE SQL RETURN f() + 1;
+step s2_drop_function_f: DROP FUNCTION f(); <waiting ...>
+step s1_commit: COMMIT;
+step s2_drop_function_f: <... completed>
+ERROR: cannot drop function f() because other objects depend on it
+
+starting permutation: s2_begin s2_drop_function_f s1_create_function_with_function s2_commit
+step s2_begin: BEGIN;
+step s2_drop_function_f: DROP FUNCTION f();
+step s1_create_function_with_function: CREATE FUNCTION foofunc() RETURNS int LANGUAGE SQL RETURN f() + 1; <waiting ...>
+step s2_commit: COMMIT;
+step s1_create_function_with_function: <... completed>
+ERROR: function f() does not exist
+
+starting permutation: s1_begin s1_create_domain_with_domain s2_drop_domain_id s1_commit
+step s1_begin: BEGIN;
+step s1_create_domain_with_domain: CREATE DOMAIN idid as id;
+step s2_drop_domain_id: DROP DOMAIN id; <waiting ...>
+step s1_commit: COMMIT;
+step s2_drop_domain_id: <... completed>
+ERROR: cannot drop type id because other objects depend on it
+
+starting permutation: s2_begin s2_drop_domain_id s1_create_domain_with_domain s2_commit
+step s2_begin: BEGIN;
+step s2_drop_domain_id: DROP DOMAIN id;
+step s1_create_domain_with_domain: CREATE DOMAIN idid as id; <waiting ...>
+step s2_commit: COMMIT;
+step s1_create_domain_with_domain: <... completed>
+ERROR: type id does not exist
+
+starting permutation: s1_begin s1_create_table_with_type s2_drop_footab_type s1_commit
+step s1_begin: BEGIN;
+step s1_create_table_with_type: CREATE TABLE tabtype(a footab);
+step s2_drop_footab_type: DROP TYPE public.footab; <waiting ...>
+step s1_commit: COMMIT;
+step s2_drop_footab_type: <... completed>
+ERROR: cannot drop type footab because other objects depend on it
+
+starting permutation: s2_begin s2_drop_footab_type s1_create_table_with_type s2_commit
+step s2_begin: BEGIN;
+step s2_drop_footab_type: DROP TYPE public.footab;
+step s1_create_table_with_type: CREATE TABLE tabtype(a footab); <waiting ...>
+step s2_commit: COMMIT;
+step s1_create_table_with_type: <... completed>
+ERROR: type footab does not exist
+
+starting permutation: s1_begin s1_create_server_with_fdw_wrapper s2_drop_fdw_wrapper s1_commit
+step s1_begin: BEGIN;
+step s1_create_server_with_fdw_wrapper: CREATE SERVER srv_fdw_wrapper FOREIGN DATA WRAPPER fdw_wrapper;
+step s2_drop_fdw_wrapper: DROP FOREIGN DATA WRAPPER fdw_wrapper RESTRICT; <waiting ...>
+step s1_commit: COMMIT;
+step s2_drop_fdw_wrapper: <... completed>
+ERROR: cannot drop foreign-data wrapper fdw_wrapper because other objects depend on it
+
+starting permutation: s2_begin s2_drop_fdw_wrapper s1_create_server_with_fdw_wrapper s2_commit
+step s2_begin: BEGIN;
+step s2_drop_fdw_wrapper: DROP FOREIGN DATA WRAPPER fdw_wrapper RESTRICT;
+step s1_create_server_with_fdw_wrapper: CREATE SERVER srv_fdw_wrapper FOREIGN DATA WRAPPER fdw_wrapper; <waiting ...>
+step s2_commit: COMMIT;
+step s1_create_server_with_fdw_wrapper: <... completed>
+ERROR: foreign-data wrapper fdw_wrapper does not exist
diff --git a/src/test/modules/test_dependencies_locks/meson.build b/src/test/modules/test_dependencies_locks/meson.build
new file mode 100644
index 0000000000..92a978ab93
--- /dev/null
+++ b/src/test/modules/test_dependencies_locks/meson.build
@@ -0,0 +1,12 @@
+# Copyright (c) 2024, PostgreSQL Global Development Group
+
+tests += {
+ 'name': 'test_dependencies_locks',
+ 'sd': meson.current_source_dir(),
+ 'bd': meson.current_build_dir(),
+ 'isolation': {
+ 'specs': [
+ 'test_dependencies_locks',
+ ],
+ },
+}
diff --git a/src/test/modules/test_dependencies_locks/specs/test_dependencies_locks.spec b/src/test/modules/test_dependencies_locks/specs/test_dependencies_locks.spec
new file mode 100644
index 0000000000..3d90a29c47
--- /dev/null
+++ b/src/test/modules/test_dependencies_locks/specs/test_dependencies_locks.spec
@@ -0,0 +1,78 @@
+setup
+{
+ CREATE SCHEMA testschema;
+ CREATE TYPE public.foo as enum ('one', 'two');
+ CREATE TYPE public.footab as enum ('three', 'four');
+ CREATE DOMAIN id AS int;
+ CREATE FUNCTION f() RETURNS int LANGUAGE SQL RETURN 1;
+ CREATE FOREIGN DATA WRAPPER fdw_wrapper;
+}
+
+teardown
+{
+ DROP FUNCTION IF EXISTS testschema.foo();
+ DROP FUNCTION IF EXISTS fooargtype(num foo);
+ DROP FUNCTION IF EXISTS footrettype();
+ DROP FUNCTION IF EXISTS foofunc();
+ DROP DOMAIN IF EXISTS idid;
+ DROP SERVER IF EXISTS srv_fdw_wrapper;
+ DROP TABLE IF EXISTS tabtype;
+ DROP SCHEMA IF EXISTS testschema;
+ DROP TYPE IF EXISTS public.foo;
+ DROP TYPE IF EXISTS public.footab;
+ DROP DOMAIN IF EXISTS id;
+ DROP FUNCTION IF EXISTS f();
+ DROP FOREIGN DATA WRAPPER IF EXISTS fdw_wrapper;
+}
+
+session "s1"
+
+step "s1_begin" { BEGIN; }
+step "s1_create_function_in_schema" { CREATE FUNCTION testschema.foo() RETURNS int AS 'select 1' LANGUAGE sql; }
+step "s1_create_function_with_argtype" { CREATE FUNCTION fooargtype(num foo) RETURNS int AS 'select 1' LANGUAGE sql; }
+step "s1_create_function_with_rettype" { CREATE FUNCTION footrettype() RETURNS id LANGUAGE sql RETURN 1; }
+step "s1_create_function_with_function" { CREATE FUNCTION foofunc() RETURNS int LANGUAGE SQL RETURN f() + 1; }
+step "s1_create_domain_with_domain" { CREATE DOMAIN idid as id; }
+step "s1_create_table_with_type" { CREATE TABLE tabtype(a footab); }
+step "s1_create_server_with_fdw_wrapper" { CREATE SERVER srv_fdw_wrapper FOREIGN DATA WRAPPER fdw_wrapper; }
+step "s1_commit" { COMMIT; }
+
+session "s2"
+
+step "s2_begin" { BEGIN; }
+step "s2_drop_schema" { DROP SCHEMA testschema; }
+step "s2_drop_foo_type" { DROP TYPE public.foo; }
+step "s2_drop_foo_rettype" { DROP DOMAIN id; }
+step "s2_drop_footab_type" { DROP TYPE public.footab; }
+step "s2_drop_function_f" { DROP FUNCTION f(); }
+step "s2_drop_domain_id" { DROP DOMAIN id; }
+step "s2_drop_fdw_wrapper" { DROP FOREIGN DATA WRAPPER fdw_wrapper RESTRICT; }
+step "s2_commit" { COMMIT; }
+
+# function - schema
+permutation "s1_begin" "s1_create_function_in_schema" "s2_drop_schema" "s1_commit"
+permutation "s2_begin" "s2_drop_schema" "s1_create_function_in_schema" "s2_commit"
+
+# function - argtype
+permutation "s1_begin" "s1_create_function_with_argtype" "s2_drop_foo_type" "s1_commit"
+permutation "s2_begin" "s2_drop_foo_type" "s1_create_function_with_argtype" "s2_commit"
+
+# function - rettype
+permutation "s1_begin" "s1_create_function_with_rettype" "s2_drop_foo_rettype" "s1_commit"
+permutation "s2_begin" "s2_drop_foo_rettype" "s1_create_function_with_rettype" "s2_commit"
+
+# function - function
+permutation "s1_begin" "s1_create_function_with_function" "s2_drop_function_f" "s1_commit"
+permutation "s2_begin" "s2_drop_function_f" "s1_create_function_with_function" "s2_commit"
+
+# domain - domain
+permutation "s1_begin" "s1_create_domain_with_domain" "s2_drop_domain_id" "s1_commit"
+permutation "s2_begin" "s2_drop_domain_id" "s1_create_domain_with_domain" "s2_commit"
+
+# table - type
+permutation "s1_begin" "s1_create_table_with_type" "s2_drop_footab_type" "s1_commit"
+permutation "s2_begin" "s2_drop_footab_type" "s1_create_table_with_type" "s2_commit"
+
+# server - foreign data wrapper
+permutation "s1_begin" "s1_create_server_with_fdw_wrapper" "s2_drop_fdw_wrapper" "s1_commit"
+permutation "s2_begin" "s2_drop_fdw_wrapper" "s1_create_server_with_fdw_wrapper" "s2_commit"
--
2.34.1
--9OVKJE/kBljR9tHR--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion:
---
src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 6 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--jlSGGOUQ0kGWBHE+--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)
If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.
Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
This affects any extension using the custom cumulative statistics API
introduced in PG18.
Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++---
src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
2 files changed, 52 insertions(+), 7 deletions(-)
100.0% src/backend/utils/activity/
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* not needed atm, and doesn't make sense with the current signature */
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
/* reset the "single counter" */
pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
TimestampTz ts = GetCurrentTimestamp();
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
if (kind_info->fixed_amount)
kind_info->reset_all_cb(ts);
else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
void *stats_data;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* should be called from backends */
Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
bool
pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
{
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/* fixed-numbered stats always exist */
- if (pgstat_get_kind_info(kind)->fixed_amount)
+ if (kind_info->fixed_amount)
return true;
return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
void
pgstat_snapshot_fixed(PgStat_Kind kind)
{
- Assert(pgstat_is_kind_valid(kind));
- Assert(pgstat_get_kind_info(kind)->fixed_amount);
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
+ Assert(kind_info->fixed_amount);
if (force_stats_snapshot_clear)
pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
/* need to be able to flush out */
- Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+ Assert(kind_info->flush_pending_cb != NULL);
if (unlikely(!pgStatPendingContext))
{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
if (entry_ref->pending == NULL)
{
- size_t entrysize = pgstat_get_kind_info(kind)->pending_size;
+ size_t entrysize = kind_info->pending_size;
Assert(entrysize != (size_t) -1);
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
PgStatShared_Common *shheader;
const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
+
/*
* Initialize refcount to 1, marking it as valid / not dropped. The entry
* can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
{
PgStat_EntryRef *entry_ref;
+ const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+ if (unlikely(kind_info == NULL))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("statistics kind %u is not known or registered", kind)));
- Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+ Assert(!kind_info->fixed_amount);
entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
if (!entry_ref || entry_ref->shared_entry->dropped)
--
2.34.1
--cdkC2WBRrpXQO1hi--
^ permalink raw reply [nested|flat] 250+ messages in thread
* Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 07:19 Bertrand Drouvot <[email protected]>
2026-07-01 08:20 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Ewan Young <[email protected]>
0 siblings, 1 reply; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-01 07:19 UTC (permalink / raw)
To: [email protected]; +Cc: Michael Paquier <[email protected]>
Hi hackers,
While reviewing [1], I got segfault(s) because I created a custom statistics
extension that I forgot to add to shared_preload_libraries. Then using one of
its function produced:
"
Core was generated by `postgres: postgres postgres [local] SELECT '.
Program terminated with signal SIGSEGV, Segmentation fault.
#0 pgstat_init_entry (kind=kind@entry=24, shhashent=shhashent@entry=0x73f6c341a740) at pgstat_shmem.c:335
335 chunk = dsa_allocate_extended(pgStatLocal.dsa,
"
Indeed, if a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call to
pgstat_register_kind(). The SQL functions are still created, and calling them
invokes pgstat functions with a kind that was never registered.
pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would dereference
NULL and segfault.
The attached patch adds runtime checks in all public-facing pgstat functions that
accept a PgStat_Kind and dereference the returned kind info:
- pgstat_prep_pending_entry()
- pgstat_fetch_entry()
- pgstat_reset()
- pgstat_reset_of_kind()
- pgstat_have_entry()
- pgstat_snapshot_fixed()
- pgstat_init_entry()
- pgstat_reset_entry()
Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.
[1]: https://postgr.es/m/akSi2txzLZWQL31Q%40bdtpg
Regards,
--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 250+ messages in thread
* Re: Prevent crash when calling pgstat functions with unregistered stats kind
2026-07-01 07:19 Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
@ 2026-07-01 08:20 ` Ewan Young <[email protected]>
2026-07-02 03:27 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
0 siblings, 1 reply; 250+ messages in thread
From: Ewan Young @ 2026-07-01 08:20 UTC (permalink / raw)
To: Bertrand Drouvot <[email protected]>; +Cc: [email protected]; Michael Paquier <[email protected]>
Hi Bertrand,
On Wed, Jul 1, 2026 at 3:20 PM Bertrand Drouvot
<[email protected]> wrote:
>
> Hi hackers,
>
> While reviewing [1], I got segfault(s) because I created a custom statistics
> extension that I forgot to add to shared_preload_libraries. Then using one of
> its function produced:
>
> "
> Core was generated by `postgres: postgres postgres [local] SELECT '.
> Program terminated with signal SIGSEGV, Segmentation fault.
> #0 pgstat_init_entry (kind=kind@entry=24, shhashent=shhashent@entry=0x73f6c341a740) at pgstat_shmem.c:335
> 335 chunk = dsa_allocate_extended(pgStatLocal.dsa,
> "
>
> Indeed, if a custom statistics extension is loaded via CREATE EXTENSION without
> being listed in shared_preload_libraries, its _PG_init() skips the call to
> pgstat_register_kind(). The SQL functions are still created, and calling them
> invokes pgstat functions with a kind that was never registered.
>
> pgstat_get_kind_info() returns NULL in this case. The existing code only
> checked this via Assert() in some paths, so non-assert builds would dereference
> NULL and segfault.
>
> The attached patch adds runtime checks in all public-facing pgstat functions that
> accept a PgStat_Kind and dereference the returned kind info:
>
> - pgstat_prep_pending_entry()
> - pgstat_fetch_entry()
> - pgstat_reset()
> - pgstat_reset_of_kind()
> - pgstat_have_entry()
> - pgstat_snapshot_fixed()
> - pgstat_init_entry()
> - pgstat_reset_entry()
>
> Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
> the kind is not known or registered.
Thanks for the patch — nice catch, and the diagnosis looks right.
One small thing: in pgstat_snapshot_fixed(), the existing
Assert(pgstat_is_kind_valid(kind)); becomes redundant after the new NULL
check. A non-NULL kind_info already implies the kind is valid (that's the
only way pgstat_get_kind_info() returns non-NULL), so the assert can never
fire. Might as well drop it and keep just the fixed_amount one.
>
> [1]: https://postgr.es/m/akSi2txzLZWQL31Q%40bdtpg
>
> Regards,
>
> --
> Bertrand Drouvot
> PostgreSQL Contributors Team
> RDS Open Source Databases
> Amazon Web Services: https://aws.amazon.com
--
Regards,
Ewan Young
^ permalink raw reply [nested|flat] 250+ messages in thread
* Re: Prevent crash when calling pgstat functions with unregistered stats kind
2026-07-01 07:19 Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 08:20 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Ewan Young <[email protected]>
@ 2026-07-02 03:27 ` Bertrand Drouvot <[email protected]>
2026-07-02 03:43 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Michael Paquier <[email protected]>
0 siblings, 1 reply; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 03:27 UTC (permalink / raw)
To: Ewan Young <[email protected]>; +Cc: [email protected]; Michael Paquier <[email protected]>
Hi Ewan,
On Wed, Jul 01, 2026 at 04:20:39PM +0800, Ewan Young wrote:
> Thanks for the patch — nice catch, and the diagnosis looks right.
Thanks for looking at it!
> One small thing: in pgstat_snapshot_fixed(), the existing
> Assert(pgstat_is_kind_valid(kind)); becomes redundant after the new NULL
> check. A non-NULL kind_info already implies the kind is valid (that's the
> only way pgstat_get_kind_info() returns non-NULL), so the assert can never
> fire. Might as well drop it and keep just the fixed_amount one.
Yeah good catch, done in the attached.
Regards,
--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 250+ messages in thread
* Re: Prevent crash when calling pgstat functions with unregistered stats kind
2026-07-01 07:19 Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 08:20 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Ewan Young <[email protected]>
2026-07-02 03:27 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
@ 2026-07-02 03:43 ` Michael Paquier <[email protected]>
2026-07-02 04:06 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
0 siblings, 1 reply; 250+ messages in thread
From: Michael Paquier @ 2026-07-02 03:43 UTC (permalink / raw)
To: Bertrand Drouvot <[email protected]>; +Cc: Ewan Young <[email protected]>; [email protected]
On Thu, Jul 02, 2026 at 03:27:18AM +0000, Bertrand Drouvot wrote:
> On Wed, Jul 01, 2026 at 04:20:39PM +0800, Ewan Young wrote:
>> One small thing: in pgstat_snapshot_fixed(), the existing
>> Assert(pgstat_is_kind_valid(kind)); becomes redundant after the new NULL
>> check. A non-NULL kind_info already implies the kind is valid (that's the
>> only way pgstat_get_kind_info() returns non-NULL), so the assert can never
>> fire. Might as well drop it and keep just the fixed_amount one.
>
> Yeah good catch, done in the attached.
I am not convinced that it is worth bothering in the core code about
this class of failures; they are just not interesting, and impossible
to miss.
It seems to me that this error is in the _PG_init() of the modules in
modules/test_custom_stats/: we should not bypass the
pgstat_register_kind() if not loading the library from
shared_preload_libraries, but let the call happen and fail.
--
Michael
Attachments:
[application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
download
^ permalink raw reply [nested|flat] 250+ messages in thread
* Re: Prevent crash when calling pgstat functions with unregistered stats kind
2026-07-01 07:19 Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 08:20 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Ewan Young <[email protected]>
2026-07-02 03:27 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-02 03:43 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Michael Paquier <[email protected]>
@ 2026-07-02 04:06 ` Bertrand Drouvot <[email protected]>
2026-07-02 04:10 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Michael Paquier <[email protected]>
0 siblings, 1 reply; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 04:06 UTC (permalink / raw)
To: Michael Paquier <[email protected]>; +Cc: Ewan Young <[email protected]>; [email protected]
Hi,
On Thu, Jul 02, 2026 at 12:43:43PM +0900, Michael Paquier wrote:
> On Thu, Jul 02, 2026 at 03:27:18AM +0000, Bertrand Drouvot wrote:
> > On Wed, Jul 01, 2026 at 04:20:39PM +0800, Ewan Young wrote:
> >> One small thing: in pgstat_snapshot_fixed(), the existing
> >> Assert(pgstat_is_kind_valid(kind)); becomes redundant after the new NULL
> >> check. A non-NULL kind_info already implies the kind is valid (that's the
> >> only way pgstat_get_kind_info() returns non-NULL), so the assert can never
> >> fire. Might as well drop it and keep just the fixed_amount one.
> >
> > Yeah good catch, done in the attached.
>
> I am not convinced that it is worth bothering in the core code about
> this class of failures; they are just not interesting, and impossible
> to miss.
>
> It seems to me that this error is in the _PG_init() of the modules in
> modules/test_custom_stats/: we should not bypass the
> pgstat_register_kind() if not loading the library from
> shared_preload_libraries, but let the call happen and fail.
I agree that the responsibility should primarily be in the extension. However,
the issue is that the NULL dereference happens inside core code (pgstat_prep_pending_entry,
etc.), and the resulting segfault(s) cause the postmaster to terminate all
backends (not just the offending session).
Given that one misconfigured extension can crash all connections on the server,
a defensive check in core seems reasonable (kind of similar to 341e9a05e7b).
Regards,
--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 250+ messages in thread
* Re: Prevent crash when calling pgstat functions with unregistered stats kind
2026-07-01 07:19 Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 08:20 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Ewan Young <[email protected]>
2026-07-02 03:27 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-02 03:43 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Michael Paquier <[email protected]>
2026-07-02 04:06 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
@ 2026-07-02 04:10 ` Michael Paquier <[email protected]>
2026-07-02 04:23 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
0 siblings, 1 reply; 250+ messages in thread
From: Michael Paquier @ 2026-07-02 04:10 UTC (permalink / raw)
To: Bertrand Drouvot <[email protected]>; +Cc: Ewan Young <[email protected]>; [email protected]
On Thu, Jul 02, 2026 at 04:06:01AM +0000, Bertrand Drouvot wrote:
> I agree that the responsibility should primarily be in the extension. However,
> the issue is that the NULL dereference happens inside core code (pgstat_prep_pending_entry,
> etc.), and the resulting segfault(s) cause the postmaster to terminate all
> backends (not just the offending session).
>
> Given that one misconfigured extension can crash all connections on the server,
> a defensive check in core seems reasonable (kind of similar to 341e9a05e7b).
Nope, this was a different thing, doable in a couple of steps:
- Load the library.
- Write custom stats.
- Stop the server, flush the stats.
- Edit the configuration, not loading the library.
- Restart the server, loading failed.
The problem of this thread ought to be blocked at its source, in the
extension itself: let's not give free hands to an extension to do what
it should not be allowed to do. There is a similar defense in
test_custom_rmgrs, as one example. We should just map to that.
--
Michael
Attachments:
[application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
download
^ permalink raw reply [nested|flat] 250+ messages in thread
* Re: Prevent crash when calling pgstat functions with unregistered stats kind
2026-07-01 07:19 Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 08:20 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Ewan Young <[email protected]>
2026-07-02 03:27 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-02 03:43 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Michael Paquier <[email protected]>
2026-07-02 04:06 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-02 04:10 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Michael Paquier <[email protected]>
@ 2026-07-02 04:23 ` Bertrand Drouvot <[email protected]>
2026-07-02 04:43 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
0 siblings, 1 reply; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 04:23 UTC (permalink / raw)
To: Michael Paquier <[email protected]>; +Cc: Ewan Young <[email protected]>; [email protected]
Hi,
On Thu, Jul 02, 2026 at 01:10:18PM +0900, Michael Paquier wrote:
> On Thu, Jul 02, 2026 at 04:06:01AM +0000, Bertrand Drouvot wrote:
> > I agree that the responsibility should primarily be in the extension. However,
> > the issue is that the NULL dereference happens inside core code (pgstat_prep_pending_entry,
> > etc.), and the resulting segfault(s) cause the postmaster to terminate all
> > backends (not just the offending session).
> >
> > Given that one misconfigured extension can crash all connections on the server,
> > a defensive check in core seems reasonable (kind of similar to 341e9a05e7b).
>
> Nope, this was a different thing, doable in a couple of steps:
> - Load the library.
> - Write custom stats.
> - Stop the server, flush the stats.
> - Edit the configuration, not loading the library.
> - Restart the server, loading failed.
>
> The problem of this thread ought to be blocked at its source, in the
> extension itself: let's not give free hands to an extension to do what
> it should not be allowed to do. There is a similar defense in
> test_custom_rmgrs, as one example. We should just map to that.
Ok but what about extensions that don't call pgstat_register_kind() at all? Your
point is that they would see the issue during the development of the extension? (If
so, I think I could agree).
Regards,
--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 250+ messages in thread
* Re: Prevent crash when calling pgstat functions with unregistered stats kind
2026-07-01 07:19 Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 08:20 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Ewan Young <[email protected]>
2026-07-02 03:27 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-02 03:43 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Michael Paquier <[email protected]>
2026-07-02 04:06 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-02 04:10 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Michael Paquier <[email protected]>
2026-07-02 04:23 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
@ 2026-07-02 04:43 ` Bertrand Drouvot <[email protected]>
2026-07-02 05:00 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Michael Paquier <[email protected]>
0 siblings, 1 reply; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 04:43 UTC (permalink / raw)
To: Michael Paquier <[email protected]>; +Cc: Ewan Young <[email protected]>; [email protected]
On Thu, Jul 02, 2026 at 04:23:16AM +0000, Bertrand Drouvot wrote:
> Hi,
>
> On Thu, Jul 02, 2026 at 01:10:18PM +0900, Michael Paquier wrote:
> > On Thu, Jul 02, 2026 at 04:06:01AM +0000, Bertrand Drouvot wrote:
> > > I agree that the responsibility should primarily be in the extension. However,
> > > the issue is that the NULL dereference happens inside core code (pgstat_prep_pending_entry,
> > > etc.), and the resulting segfault(s) cause the postmaster to terminate all
> > > backends (not just the offending session).
> > >
> > > Given that one misconfigured extension can crash all connections on the server,
> > > a defensive check in core seems reasonable (kind of similar to 341e9a05e7b).
> >
> > Nope, this was a different thing, doable in a couple of steps:
> > - Load the library.
> > - Write custom stats.
> > - Stop the server, flush the stats.
> > - Edit the configuration, not loading the library.
> > - Restart the server, loading failed.
> >
> > The problem of this thread ought to be blocked at its source, in the
> > extension itself: let's not give free hands to an extension to do what
> > it should not be allowed to do. There is a similar defense in
> > test_custom_rmgrs, as one example. We should just map to that.
>
> Ok but what about extensions that don't call pgstat_register_kind() at all? Your
> point is that they would see the issue during the development of the extension? (If
> so, I think I could agree).
Something like in the attached?
Regards,
--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 250+ messages in thread
* Re: Prevent crash when calling pgstat functions with unregistered stats kind
2026-07-01 07:19 Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 08:20 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Ewan Young <[email protected]>
2026-07-02 03:27 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-02 03:43 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Michael Paquier <[email protected]>
2026-07-02 04:06 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-02 04:10 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Michael Paquier <[email protected]>
2026-07-02 04:23 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-02 04:43 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
@ 2026-07-02 05:00 ` Michael Paquier <[email protected]>
2026-07-02 05:02 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
0 siblings, 1 reply; 250+ messages in thread
From: Michael Paquier @ 2026-07-02 05:00 UTC (permalink / raw)
To: Bertrand Drouvot <[email protected]>; +Cc: Ewan Young <[email protected]>; [email protected]
On Thu, Jul 02, 2026 at 04:43:32AM +0000, Bertrand Drouvot wrote:
> On Thu, Jul 02, 2026 at 04:23:16AM +0000, Bertrand Drouvot wrote:
>> Ok but what about extensions that don't call pgstat_register_kind() at all? Your
>> point is that they would see the issue during the development of the extension? (If
>> so, I think I could agree).
Extensions doing custom stats have to call the register API, or
they're broken. This is the same assumption as custom RMGRs. There
are many ways to break the backend if you don't know what you do, just
take hooks for example. That's just one of them.
> Something like in the attached?
Yes, that looks OK here.
--
Michael
Attachments:
[application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
download
^ permalink raw reply [nested|flat] 250+ messages in thread
* Re: Prevent crash when calling pgstat functions with unregistered stats kind
2026-07-01 07:19 Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 08:20 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Ewan Young <[email protected]>
2026-07-02 03:27 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-02 03:43 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Michael Paquier <[email protected]>
2026-07-02 04:06 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-02 04:10 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Michael Paquier <[email protected]>
2026-07-02 04:23 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-02 04:43 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-02 05:00 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Michael Paquier <[email protected]>
@ 2026-07-02 05:02 ` Bertrand Drouvot <[email protected]>
2026-07-02 06:53 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Michael Paquier <[email protected]>
0 siblings, 1 reply; 250+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 05:02 UTC (permalink / raw)
To: Michael Paquier <[email protected]>; +Cc: Ewan Young <[email protected]>; [email protected]
Hi,
On Thu, Jul 02, 2026 at 02:00:06PM +0900, Michael Paquier wrote:
> On Thu, Jul 02, 2026 at 04:43:32AM +0000, Bertrand Drouvot wrote:
> > On Thu, Jul 02, 2026 at 04:23:16AM +0000, Bertrand Drouvot wrote:
> >> Ok but what about extensions that don't call pgstat_register_kind() at all? Your
> >> point is that they would see the issue during the development of the extension? (If
> >> so, I think I could agree).
>
> Extensions doing custom stats have to call the register API, or
> they're broken. This is the same assumption as custom RMGRs. There
> are many ways to break the backend if you don't know what you do, just
> take hooks for example. That's just one of them.
>
> > Something like in the attached?
>
> Yes, that looks OK here.
That makes sense, I do agree.
Regards,
--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 250+ messages in thread
* Re: Prevent crash when calling pgstat functions with unregistered stats kind
2026-07-01 07:19 Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 08:20 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Ewan Young <[email protected]>
2026-07-02 03:27 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-02 03:43 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Michael Paquier <[email protected]>
2026-07-02 04:06 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-02 04:10 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Michael Paquier <[email protected]>
2026-07-02 04:23 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-02 04:43 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-02 05:00 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Michael Paquier <[email protected]>
2026-07-02 05:02 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
@ 2026-07-02 06:53 ` Michael Paquier <[email protected]>
0 siblings, 0 replies; 250+ messages in thread
From: Michael Paquier @ 2026-07-02 06:53 UTC (permalink / raw)
To: Bertrand Drouvot <[email protected]>; +Cc: Ewan Young <[email protected]>; [email protected]
On Thu, Jul 02, 2026 at 05:02:36AM +0000, Bertrand Drouvot wrote:
> That makes sense, I do agree.
Done that now down to v19, thanks, in the shape of some pure code
deletion.
--
Michael
Attachments:
[application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
download
^ permalink raw reply [nested|flat] 250+ messages in thread
end of thread, other threads:[~2026-07-02 06:53 UTC | newest]
Thread overview: 250+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2024-03-29 15:43 [PATCH v4] Avoid orphaned objects dependencies Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 07:19 Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-01 08:20 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Ewan Young <[email protected]>
2026-07-02 03:27 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-02 03:43 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Michael Paquier <[email protected]>
2026-07-02 04:06 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-02 04:10 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Michael Paquier <[email protected]>
2026-07-02 04:23 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-02 04:43 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-02 05:00 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Michael Paquier <[email protected]>
2026-07-02 05:02 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <[email protected]>
2026-07-02 06:53 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Michael Paquier <[email protected]>
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox