agora inbox for [email protected]
help / color / mirror / Atom feed[PATCH v4 3/3] Replace matview WITH OLD DATA
434+ messages / 4 participants
[nested] [flat]
* [PATCH v4 3/3] Replace matview WITH OLD DATA
@ 2024-07-26 21:33 Erik Wienhold <[email protected]>
0 siblings, 0 replies; 434+ messages in thread
From: Erik Wienhold @ 2024-07-26 21:33 UTC (permalink / raw)
---
.../sgml/ref/create_materialized_view.sgml | 16 +++++++++--
src/backend/commands/createas.c | 26 +++++++++++------
src/backend/parser/gram.y | 16 +++++++++++
src/include/nodes/primnodes.h | 1 +
src/test/regress/expected/matview.out | 28 +++++++++++++++++++
src/test/regress/sql/matview.sql | 15 ++++++++++
6 files changed, 90 insertions(+), 12 deletions(-)
diff --git a/doc/src/sgml/ref/create_materialized_view.sgml b/doc/src/sgml/ref/create_materialized_view.sgml
index 5e03320eb7..1352e9de40 100644
--- a/doc/src/sgml/ref/create_materialized_view.sgml
+++ b/doc/src/sgml/ref/create_materialized_view.sgml
@@ -27,7 +27,7 @@ CREATE [ OR REPLACE ] MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_nam
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
[ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
AS <replaceable>query</replaceable>
- [ WITH [ NO ] DATA ]
+ [ WITH [ NO | OLD ] DATA ]
</synopsis>
</refsynopsisdiv>
@@ -37,7 +37,8 @@ CREATE [ OR REPLACE ] MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_nam
<para>
<command>CREATE MATERIALIZED VIEW</command> defines a materialized view of
a query. The query is executed and used to populate the view at the time
- the command is issued (unless <command>WITH NO DATA</command> is used) and may be
+ the command is issued (unless <command>WITH NO DATA</command> or
+ <command>WITH OLD DATA</command> is used) and may be
refreshed later using <command>REFRESH MATERIALIZED VIEW</command>.
</para>
@@ -162,7 +163,7 @@ CREATE [ OR REPLACE ] MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_nam
</varlistentry>
<varlistentry>
- <term><literal>WITH [ NO ] DATA</literal></term>
+ <term><literal>WITH [ NO | OLD ] DATA</literal></term>
<listitem>
<para>
This clause specifies whether or not the materialized view should be
@@ -170,6 +171,15 @@ CREATE [ OR REPLACE ] MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_nam
flagged as unscannable and cannot be queried until <command>REFRESH
MATERIALIZED VIEW</command> is used.
</para>
+
+ <para>
+ The form <command>WITH OLD DATA</command> keeps the already stored data
+ when replacing an existing materialized view to keep it populated. For
+ newly created materialized views, this has the same effect as
+ <command>WITH DATA</command>. Use this form if you want to use
+ <command>REFRESH MATERIALIZED VIEW CONCURRENTLY</command> as it requires
+ a populated materialized view.
+ </para>
</listitem>
</varlistentry>
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index d4bc5e5c08..8db000554c 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -332,18 +332,26 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt,
/* An existing materialized view can be replaced. */
if (is_matview && into->replace)
{
- RefreshMatViewStmt *refresh;
-
/* Change the relation to match the new query and other options. */
- (void) create_ctas_nodata(query->targetList, into);
+ address = create_ctas_nodata(query->targetList, into);
- /* Refresh the materialized view with a fake statement. */
- refresh = makeNode(RefreshMatViewStmt);
- refresh->relation = into->rel;
- refresh->skipData = into->skipData;
- refresh->concurrent = false;
+ /*
+ * Refresh the materialized view with a fake statement unless we
+ * must keep the old data.
+ */
+ if (!into->keepData)
+ {
+ RefreshMatViewStmt *refresh;
+
+ refresh = makeNode(RefreshMatViewStmt);
+ refresh->relation = into->rel;
+ refresh->skipData = into->skipData;
+ refresh->concurrent = false;
+
+ address = ExecRefreshMatView(refresh, NULL, NULL);
+ }
- return ExecRefreshMatView(refresh, NULL, NULL);
+ return address;
}
return InvalidObjectAddress;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index dadc6b630a..1be1a95cd9 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -4838,6 +4838,22 @@ CreateMatViewStmt:
$7->replace = true;
$$ = (Node *) ctas;
}
+ | CREATE OR REPLACE OptNoLog MATERIALIZED VIEW create_mv_target AS SelectStmt WITH OLD DATA_P
+ {
+ CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt);
+
+ ctas->query = $9;
+ ctas->into = $7;
+ ctas->objtype = OBJECT_MATVIEW;
+ ctas->is_select_into = false;
+ ctas->if_not_exists = false;
+ /* cram additional flags into the IntoClause */
+ $7->rel->relpersistence = $4;
+ $7->skipData = false;
+ $7->keepData = true;
+ $7->replace = true;
+ $$ = (Node *) ctas;
+ }
;
create_mv_target:
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 533fa847ab..6fa3291c64 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -168,6 +168,7 @@ typedef struct IntoClause
/* materialized view's SELECT query */
struct Query *viewQuery pg_node_attr(query_jumble_ignore);
bool skipData; /* true for WITH NO DATA */
+ bool keepData; /* true for WITH OLD DATA */
bool replace; /* replace existing matview? */
} IntoClause;
diff --git a/src/test/regress/expected/matview.out b/src/test/regress/expected/matview.out
index cefd0d442c..47dfd88bff 100644
--- a/src/test/regress/expected/matview.out
+++ b/src/test/regress/expected/matview.out
@@ -751,6 +751,23 @@ SELECT * FROM mvtest_replace;
3
(1 row)
+-- replace query but keep old data
+CREATE OR REPLACE MATERIALIZED VIEW mvtest_replace AS
+ SELECT 5 AS a
+ WITH OLD DATA;
+SELECT * FROM mvtest_replace;
+ a
+---
+ 3
+(1 row)
+
+REFRESH MATERIALIZED VIEW mvtest_replace;
+SELECT * FROM mvtest_replace;
+ a
+---
+ 5
+(1 row)
+
-- add column
CREATE OR REPLACE MATERIALIZED VIEW mvtest_replace AS
SELECT 4 AS a, 1 b;
@@ -905,3 +922,14 @@ ERROR: syntax error at or near "NOT"
LINE 1: CREATE OR REPLACE MATERIALIZED VIEW IF NOT EXISTS mvtest_rep...
^
DROP MATERIALIZED VIEW mvtest_replace;
+-- Create new matview WITH OLD DATA. This populates the new matview as if
+-- WITH DATA had been specified.
+CREATE OR REPLACE MATERIALIZED VIEW mvtest_replace AS
+ SELECT 17 AS a
+ WITH OLD DATA;
+SELECT * FROM mvtest_replace;
+ a
+----
+ 17
+(1 row)
+
diff --git a/src/test/regress/sql/matview.sql b/src/test/regress/sql/matview.sql
index c12f0243c9..b268237c24 100644
--- a/src/test/regress/sql/matview.sql
+++ b/src/test/regress/sql/matview.sql
@@ -338,6 +338,14 @@ SELECT * FROM mvtest_replace; -- error: not populated
REFRESH MATERIALIZED VIEW mvtest_replace;
SELECT * FROM mvtest_replace;
+-- replace query but keep old data
+CREATE OR REPLACE MATERIALIZED VIEW mvtest_replace AS
+ SELECT 5 AS a
+ WITH OLD DATA;
+SELECT * FROM mvtest_replace;
+REFRESH MATERIALIZED VIEW mvtest_replace;
+SELECT * FROM mvtest_replace;
+
-- add column
CREATE OR REPLACE MATERIALIZED VIEW mvtest_replace AS
SELECT 4 AS a, 1 b;
@@ -422,3 +430,10 @@ CREATE OR REPLACE MATERIALIZED VIEW IF NOT EXISTS mvtest_replace AS
SELECT 1 AS a;
DROP MATERIALIZED VIEW mvtest_replace;
+
+-- Create new matview WITH OLD DATA. This populates the new matview as if
+-- WITH DATA had been specified.
+CREATE OR REPLACE MATERIALIZED VIEW mvtest_replace AS
+ SELECT 17 AS a
+ WITH OLD DATA;
+SELECT * FROM mvtest_replace;
--
2.47.0
--hht4bymbvbva5al5--
^ permalink raw reply [nested|flat] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ 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; 434+ 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] 434+ messages in thread
end of thread, other threads:[~2026-07-02 06:53 UTC | newest]
Thread overview: 434+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2024-07-26 21:33 [PATCH v4 3/3] Replace matview WITH OLD DATA Erik Wienhold <[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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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