agora inbox for pgsql-hackers@postgresql.org  
help / color / mirror / Atom feed
[PATCH v23 1/8] Row pattern recognition patch for raw parser.
656+ messages / 4 participants
[nested] [flat]

* [PATCH v23 1/8] Row pattern recognition patch for raw parser.
@ 2024-10-25 03:56 Tatsuo Ishii <ishii@postgresql.org>
  0 siblings, 0 replies; 656+ messages in thread

From: Tatsuo Ishii @ 2024-10-25 03:56 UTC (permalink / raw)

---
 src/backend/parser/gram.y       | 221 ++++++++++++++++++++++++++++++--
 src/include/nodes/parsenodes.h  |  67 ++++++++++
 src/include/parser/kwlist.h     |   8 ++
 src/include/parser/parse_node.h |   1 +
 4 files changed, 286 insertions(+), 11 deletions(-)

diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index baca4059d2..ddfee6a652 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -664,6 +664,21 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				json_object_constructor_null_clause_opt
 				json_array_constructor_null_clause_opt
 
+%type <target>	row_pattern_measure_item
+				row_pattern_definition
+%type <node>	opt_row_pattern_common_syntax
+				opt_row_pattern_skip_to
+				row_pattern_subset_item
+				row_pattern_term
+%type <list>	opt_row_pattern_measures
+				row_pattern_measure_list
+				row_pattern_definition_list
+				opt_row_pattern_subset_clause
+				row_pattern_subset_list
+				row_pattern_subset_rhs
+				row_pattern
+%type <boolean>	opt_row_pattern_initial_or_seek
+				first_or_last
 
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -707,7 +722,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
 
 	DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
-	DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DEPTH DESC
+	DEFERRABLE DEFERRED DEFINE DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DEPTH DESC
 	DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P
 	DOUBLE_P DROP
 
@@ -723,7 +738,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	HANDLER HAVING HEADER_P HOLD HOUR_P
 
 	IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P INCLUDE
-	INCLUDING INCREMENT INDENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
+	INCLUDING INCREMENT INDENT INDEX INDEXES INHERIT INHERITS INITIAL INITIALLY INLINE_P
 	INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
 	INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
 
@@ -736,7 +751,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
 	LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED
 
-	MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE MERGE_ACTION METHOD
+	MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MEASURES MERGE MERGE_ACTION METHOD
 	MINUTE_P MINVALUE MODE MONTH_P MOVE
 
 	NAME_P NAMES NATIONAL NATURAL NCHAR NESTED NEW NEXT NFC NFD NFKC NFKD NO
@@ -748,8 +763,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	ORDER ORDINALITY OTHERS OUT_P OUTER_P
 	OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER
 
-	PARALLEL PARAMETER PARSER PARTIAL PARTITION PASSING PASSWORD PATH
-	PERIOD PLACING PLAN PLANS POLICY
+	PARALLEL PARAMETER PARSER PARTIAL PARTITION PASSING PASSWORD PAST
+	PATH PATTERN_P PERIOD PERMUTE PLACING PLAN PLANS POLICY
+
 	POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
 	PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION
 
@@ -760,12 +776,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	RESET RESTART RESTRICT RETURN RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
 	ROUTINE ROUTINES ROW ROWS RULE
 
-	SAVEPOINT SCALAR SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT
+	SAVEPOINT SCALAR SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SEEK SELECT
 	SEQUENCE SEQUENCES
+
 	SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW
 	SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SOURCE SQL_P STABLE STANDALONE_P
 	START STATEMENT STATISTICS STDIN STDOUT STORAGE STORED STRICT_P STRING_P STRIP_P
-	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P SYSTEM_USER
+	SUBSCRIPTION SUBSET SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P SYSTEM_USER
 
 	TABLE TABLES TABLESAMPLE TABLESPACE TARGET TEMP TEMPLATE TEMPORARY TEXT_P THEN
 	TIES TIME TIMESTAMP TO TRAILING TRANSACTION TRANSFORM
@@ -874,6 +891,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %nonassoc	UNBOUNDED NESTED /* ideally would have same precedence as IDENT */
 %nonassoc	IDENT PARTITION RANGE ROWS GROUPS PRECEDING FOLLOWING CUBE ROLLUP
 			SET KEYS OBJECT_P SCALAR VALUE_P WITH WITHOUT PATH
+			MEASURES AFTER INITIAL SEEK PATTERN_P
 %left		Op OPERATOR		/* multi-character ops and user-defined operators */
 %left		'+' '-'
 %left		'*' '/' '%'
@@ -16279,7 +16297,8 @@ over_clause: OVER window_specification
 		;
 
 window_specification: '(' opt_existing_window_name opt_partition_clause
-						opt_sort_clause opt_frame_clause ')'
+						opt_sort_clause opt_row_pattern_measures opt_frame_clause
+						opt_row_pattern_common_syntax ')'
 				{
 					WindowDef  *n = makeNode(WindowDef);
 
@@ -16287,10 +16306,12 @@ window_specification: '(' opt_existing_window_name opt_partition_clause
 					n->refname = $2;
 					n->partitionClause = $3;
 					n->orderClause = $4;
+					n->rowPatternMeasures = $5;
 					/* copy relevant fields of opt_frame_clause */
-					n->frameOptions = $5->frameOptions;
-					n->startOffset = $5->startOffset;
-					n->endOffset = $5->endOffset;
+					n->frameOptions = $6->frameOptions;
+					n->startOffset = $6->startOffset;
+					n->endOffset = $6->endOffset;
+					n->rpCommonSyntax = (RPCommonSyntax *)$7;
 					n->location = @1;
 					$$ = n;
 				}
@@ -16314,6 +16335,31 @@ opt_partition_clause: PARTITION BY expr_list		{ $$ = $3; }
 			| /*EMPTY*/								{ $$ = NIL; }
 		;
 
+/*
+ * ROW PATTERN_P MEASURES
+ */
+opt_row_pattern_measures: MEASURES row_pattern_measure_list	{ $$ = $2; }
+			| /*EMPTY*/								{ $$ = NIL; }
+		;
+
+row_pattern_measure_list:
+			row_pattern_measure_item
+					{ $$ = list_make1($1); }
+			| row_pattern_measure_list ',' row_pattern_measure_item
+					{ $$ = lappend($1, $3); }
+		;
+
+row_pattern_measure_item:
+			a_expr AS ColLabel
+				{
+					$$ = makeNode(ResTarget);
+					$$->name = $3;
+					$$->indirection = NIL;
+					$$->val = (Node *) $1;
+					$$->location = @1;
+				}
+		;
+
 /*
  * For frame clauses, we return a WindowDef, but only some fields are used:
  * frameOptions, startOffset, and endOffset.
@@ -16473,6 +16519,143 @@ opt_window_exclusion_clause:
 			| /*EMPTY*/				{ $$ = 0; }
 		;
 
+opt_row_pattern_common_syntax:
+opt_row_pattern_skip_to opt_row_pattern_initial_or_seek
+				PATTERN_P '(' row_pattern ')'
+				opt_row_pattern_subset_clause
+				DEFINE row_pattern_definition_list
+			{
+				RPCommonSyntax *n = makeNode(RPCommonSyntax);
+				n->rpSkipTo = ((RPCommonSyntax *)$1)->rpSkipTo;
+				n->rpSkipVariable = ((RPCommonSyntax *)$1)->rpSkipVariable;
+				n->initial = $2;
+				n->rpPatterns = $5;
+				n->rpSubsetClause = $7;
+				n->rpDefs = $9;
+				$$ = (Node *) n;
+			}
+			| /*EMPTY*/		{ $$ = NULL; }
+	;
+
+opt_row_pattern_skip_to:
+			AFTER MATCH SKIP TO NEXT ROW
+				{
+					RPCommonSyntax *n = makeNode(RPCommonSyntax);
+					n->rpSkipTo = ST_NEXT_ROW;
+					n->rpSkipVariable = NULL;
+					$$ = (Node *) n;
+			}
+			| AFTER MATCH SKIP PAST LAST_P ROW
+				{
+					RPCommonSyntax *n = makeNode(RPCommonSyntax);
+					n->rpSkipTo = ST_PAST_LAST_ROW;
+					n->rpSkipVariable = NULL;
+					$$ = (Node *) n;
+				}
+			| AFTER MATCH SKIP TO first_or_last ColId
+				{
+					RPCommonSyntax *n = makeNode(RPCommonSyntax);
+					n->rpSkipTo = $5? ST_FIRST_VARIABLE : ST_LAST_VARIABLE;
+					n->rpSkipVariable = $6;
+					$$ = (Node *) n;
+				}
+/*
+			| AFTER MATCH SKIP TO LAST_P ColId		%prec LAST_P
+				{
+					RPCommonSyntax *n = makeNode(RPCommonSyntax);
+					n->rpSkipTo = ST_LAST_VARIABLE;
+					n->rpSkipVariable = $6;
+					$$ = n;
+				}
+			| AFTER MATCH SKIP TO ColId
+				{
+					RPCommonSyntax *n = makeNode(RPCommonSyntax);
+					n->rpSkipTo = ST_VARIABLE;
+					n->rpSkipVariable = $5;
+					$$ = n;
+				}
+*/
+			| /*EMPTY*/
+				{
+					RPCommonSyntax *n = makeNode(RPCommonSyntax);
+					/* temporary set default to ST_NEXT_ROW */
+					n->rpSkipTo = ST_PAST_LAST_ROW;
+					n->rpSkipVariable = NULL;
+					$$ = (Node *) n;
+				}
+	;
+
+first_or_last:
+			FIRST_P		{ $$ = true; }
+			| LAST_P	{ $$ = false; }
+	;
+
+opt_row_pattern_initial_or_seek:
+			INITIAL			{ $$ = true; }
+			| SEEK
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("SEEK is not supported"),
+							 errhint("Use INITIAL."),
+							 parser_errposition(@1)));
+				}
+			| /*EMPTY*/		{ $$ = true; }
+		;
+
+row_pattern:
+			row_pattern_term							{ $$ = list_make1($1); }
+			| row_pattern row_pattern_term				{ $$ = lappend($1, $2); }
+		;
+
+row_pattern_term:
+			ColId	{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "", (Node *)makeString($1), NULL, @1); }
+			| ColId '*'	{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "*", (Node *)makeString($1), NULL, @1); }
+			| ColId '+'	{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "+", (Node *)makeString($1), NULL, @1); }
+			| ColId '?'	{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "?", (Node *)makeString($1), NULL, @1); }
+		;
+
+opt_row_pattern_subset_clause:
+			SUBSET row_pattern_subset_list	{ $$ = $2; }
+			| /*EMPTY*/												{ $$ = NIL; }
+		;
+
+row_pattern_subset_list:
+			row_pattern_subset_item									{ $$ = list_make1($1); }
+			| row_pattern_subset_list ',' row_pattern_subset_item	{ $$ = lappend($1, $3); }
+			| /*EMPTY*/												{ $$ = NIL; }
+		;
+
+row_pattern_subset_item: ColId '=' '(' row_pattern_subset_rhs ')'
+			{
+				RPSubsetItem *n = makeNode(RPSubsetItem);
+				n->name = $1;
+				n->rhsVariable = $4;
+				$$ = (Node *) n;
+			}
+		;
+
+row_pattern_subset_rhs:
+			ColId								{ $$ = list_make1(makeStringConst($1, @1)); }
+			| row_pattern_subset_rhs ',' ColId	{ $$ = lappend($1, makeStringConst($3, @1)); }
+			| /*EMPTY*/							{ $$ = NIL; }
+		;
+
+row_pattern_definition_list:
+			row_pattern_definition										{ $$ = list_make1($1); }
+			| row_pattern_definition_list ',' row_pattern_definition	{ $$ = lappend($1, $3); }
+		;
+
+row_pattern_definition:
+			ColId AS a_expr
+				{
+					$$ = makeNode(ResTarget);
+					$$->name = $1;
+					$$->indirection = NIL;
+					$$->val = (Node *) $3;
+					$$->location = @1;
+				}
+		;
 
 /*
  * Supporting nonterminals for expressions.
@@ -17683,6 +17866,7 @@ unreserved_keyword:
 			| INDEXES
 			| INHERIT
 			| INHERITS
+			| INITIAL
 			| INLINE_P
 			| INPUT_P
 			| INSENSITIVE
@@ -17711,6 +17895,7 @@ unreserved_keyword:
 			| MATCHED
 			| MATERIALIZED
 			| MAXVALUE
+			| MEASURES
 			| MERGE
 			| METHOD
 			| MINUTE_P
@@ -17755,8 +17940,11 @@ unreserved_keyword:
 			| PARTITION
 			| PASSING
 			| PASSWORD
+			| PAST
 			| PATH
+			| PATTERN_P
 			| PERIOD
+			| PERMUTE
 			| PLAN
 			| PLANS
 			| POLICY
@@ -17808,6 +17996,7 @@ unreserved_keyword:
 			| SEARCH
 			| SECOND_P
 			| SECURITY
+			| SEEK
 			| SEQUENCE
 			| SEQUENCES
 			| SERIALIZABLE
@@ -17835,6 +18024,7 @@ unreserved_keyword:
 			| STRING_P
 			| STRIP_P
 			| SUBSCRIPTION
+			| SUBSET
 			| SUPPORT
 			| SYSID
 			| SYSTEM_P
@@ -18029,6 +18219,7 @@ reserved_keyword:
 			| CURRENT_USER
 			| DEFAULT
 			| DEFERRABLE
+			| DEFINE
 			| DESC
 			| DISTINCT
 			| DO
@@ -18192,6 +18383,7 @@ bare_label_keyword:
 			| DEFAULTS
 			| DEFERRABLE
 			| DEFERRED
+			| DEFINE
 			| DEFINER
 			| DELETE_P
 			| DELIMITER
@@ -18269,6 +18461,7 @@ bare_label_keyword:
 			| INDEXES
 			| INHERIT
 			| INHERITS
+			| INITIAL
 			| INITIALLY
 			| INLINE_P
 			| INNER_P
@@ -18323,6 +18516,7 @@ bare_label_keyword:
 			| MATCHED
 			| MATERIALIZED
 			| MAXVALUE
+			| MEASURES
 			| MERGE
 			| MERGE_ACTION
 			| METHOD
@@ -18379,8 +18573,11 @@ bare_label_keyword:
 			| PARTITION
 			| PASSING
 			| PASSWORD
+			| PAST
 			| PATH
+			| PATTERN_P
 			| PERIOD
+			| PERMUTE
 			| PLACING
 			| PLAN
 			| PLANS
@@ -18438,6 +18635,7 @@ bare_label_keyword:
 			| SCROLL
 			| SEARCH
 			| SECURITY
+			| SEEK
 			| SELECT
 			| SEQUENCE
 			| SEQUENCES
@@ -18471,6 +18669,7 @@ bare_label_keyword:
 			| STRING_P
 			| STRIP_P
 			| SUBSCRIPTION
+			| SUBSET
 			| SUBSTRING
 			| SUPPORT
 			| SYMMETRIC
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index b40b661ec8..685ca438b1 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -552,6 +552,48 @@ typedef struct SortBy
 	ParseLoc	location;		/* operator location, or -1 if none/unknown */
 } SortBy;
 
+/*
+ * AFTER MATCH row pattern skip to types in row pattern common syntax
+ */
+typedef enum RPSkipTo
+{
+	ST_NONE,					/* AFTER MATCH omitted */
+	ST_NEXT_ROW,				/* SKIP TO NEXT ROW */
+	ST_PAST_LAST_ROW,			/* SKIP TO PAST LAST ROW */
+	ST_FIRST_VARIABLE,			/* SKIP TO FIRST variable name */
+	ST_LAST_VARIABLE,			/* SKIP TO LAST variable name */
+	ST_VARIABLE					/* SKIP TO variable name */
+}			RPSkipTo;
+
+/*
+ * Row Pattern SUBSET clause item
+ */
+typedef struct RPSubsetItem
+{
+	NodeTag		type;
+	char	   *name;			/* Row Pattern SUBSET clause variable name */
+	List	   *rhsVariable;	/* Row Pattern SUBSET rhs variables (list of
+								 * char *string) */
+}			RPSubsetItem;
+
+/*
+ * RowPatternCommonSyntax - raw representation of row pattern common syntax
+ *
+ */
+typedef struct RPCommonSyntax
+{
+	NodeTag		type;
+	RPSkipTo	rpSkipTo;		/* Row Pattern AFTER MATCH SKIP type */
+	char	   *rpSkipVariable; /* Row Pattern Skip To variable name, if any */
+	bool		initial;		/* true if <row pattern initial or seek> is
+								 * initial */
+	List	   *rpPatterns;		/* PATTERN variables (list of A_Expr) */
+	List	   *rpSubsetClause; /* row pattern subset clause (list of
+								 * RPSubsetItem), if any */
+	List	   *rpDefs;			/* row pattern definitions clause (list of
+								 * ResTarget) */
+}			RPCommonSyntax;
+
 /*
  * WindowDef - raw representation of WINDOW and OVER clauses
  *
@@ -567,6 +609,9 @@ typedef struct WindowDef
 	char	   *refname;		/* referenced window name, if any */
 	List	   *partitionClause;	/* PARTITION BY expression list */
 	List	   *orderClause;	/* ORDER BY (list of SortBy) */
+	List	   *rowPatternMeasures; /* row pattern measures (list of
+									 * ResTarget) */
+	RPCommonSyntax *rpCommonSyntax; /* row pattern common syntax */
 	int			frameOptions;	/* frame_clause options, see below */
 	Node	   *startOffset;	/* expression for starting bound, if any */
 	Node	   *endOffset;		/* expression for ending bound, if any */
@@ -1530,6 +1575,11 @@ typedef struct GroupingSet
  * the orderClause might or might not be copied (see copiedOrder); the framing
  * options are never copied, per spec.
  *
+ * "defineClause" is Row Pattern Recognition DEFINE clause (list of
+ * TargetEntry). TargetEntry.resname represents row pattern definition
+ * variable name. "patternVariable" and "patternRegexp" represents PATTERN
+ * clause.
+ *
  * The information relevant for the query jumbling is the partition clause
  * type and its bounds.
  */
@@ -1559,6 +1609,23 @@ typedef struct WindowClause
 	Index		winref;			/* ID referenced by window functions */
 	/* did we copy orderClause from refname? */
 	bool		copiedOrder pg_node_attr(query_jumble_ignore);
+	/* Row Pattern AFTER MACH SKIP clause */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	char	   *rpSkipVariable; /* Row Pattern Skip To variable */
+	bool		initial;		/* true if <row pattern initial or seek> is
+								 * initial */
+	/* Row Pattern DEFINE clause (list of TargetEntry) */
+	List	   *defineClause;
+	/* Row Pattern DEFINE variable initial names (list of String) */
+	List	   *defineInitial;
+	/* Row Pattern PATTERN variable name (list of String) */
+	List	   *patternVariable;
+
+	/*
+	 * Row Pattern PATTERN regular expression quantifier ('+' or ''. list of
+	 * String)
+	 */
+	List	   *patternRegexp;
 } WindowClause;
 
 /*
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 899d64ad55..f52d797615 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -129,6 +129,7 @@ PG_KEYWORD("default", DEFAULT, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("defaults", DEFAULTS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("deferrable", DEFERRABLE, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("deferred", DEFERRED, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("define", DEFINE, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("definer", DEFINER, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("delete", DELETE_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("delimiter", DELIMITER, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -215,6 +216,7 @@ PG_KEYWORD("index", INDEX, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("indexes", INDEXES, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("inherit", INHERIT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("inherits", INHERITS, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("initial", INITIAL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("initially", INITIALLY, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("inline", INLINE_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("inner", INNER_P, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
@@ -273,6 +275,7 @@ PG_KEYWORD("match", MATCH, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("matched", MATCHED, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("materialized", MATERIALIZED, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("maxvalue", MAXVALUE, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("measures", MEASURES, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("merge", MERGE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("merge_action", MERGE_ACTION, COL_NAME_KEYWORD, BARE_LABEL)
 PG_KEYWORD("method", METHOD, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -337,8 +340,11 @@ PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("past", PAST, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("path", PATH, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("pattern", PATTERN_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("period", PERIOD, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("permute", PERMUTE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("plan", PLAN, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -399,6 +405,7 @@ PG_KEYWORD("scroll", SCROLL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("search", SEARCH, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("second", SECOND_P, UNRESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("security", SECURITY, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("seek", SEEK, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("select", SELECT, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("sequence", SEQUENCE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("sequences", SEQUENCES, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -432,6 +439,7 @@ PG_KEYWORD("strict", STRICT_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("string", STRING_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("strip", STRIP_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("subscription", SUBSCRIPTION, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("subset", SUBSET, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("substring", SUBSTRING, COL_NAME_KEYWORD, BARE_LABEL)
 PG_KEYWORD("support", SUPPORT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("symmetric", SYMMETRIC, RESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 2375e95c10..737f8a9e0e 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -51,6 +51,7 @@ typedef enum ParseExprKind
 	EXPR_KIND_WINDOW_FRAME_RANGE,	/* window frame clause with RANGE */
 	EXPR_KIND_WINDOW_FRAME_ROWS,	/* window frame clause with ROWS */
 	EXPR_KIND_WINDOW_FRAME_GROUPS,	/* window frame clause with GROUPS */
+	EXPR_KIND_RPR_DEFINE,		/* DEFINE */
 	EXPR_KIND_SELECT_TARGET,	/* SELECT target list item */
 	EXPR_KIND_INSERT_TARGET,	/* INSERT target list item */
 	EXPR_KIND_UPDATE_SOURCE,	/* UPDATE assignment source item */
-- 
2.25.1


----Next_Part(Fri_Oct_25_13_04_53_2024_648)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v23-0002-Row-pattern-recognition-patch-parse-analysis.patch"



^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg
---
 src/backend/utils/activity/pgstat.c       | 46 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 7 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..540db1ef115 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
-	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1338,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--cdkC2WBRrpXQO1hi--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 05:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 05:43 UTC (permalink / raw)

If a custom statistics extension is loaded via CREATE EXTENSION without
being listed in shared_preload_libraries, its _PG_init() skips the call
to pgstat_register_kind(). The SQL functions are still created, and
calling them invokes pgstat functions with a kind that was never registered.

pgstat_get_kind_info() returns NULL in this case. The existing code only
checked this via Assert() in some paths, so non-assert builds would
dereference NULL and segfault.

Add runtime checks in all public-facing pgstat functions that accept a
PgStat_Kind and dereference the returned kind info:

  - pgstat_prep_pending_entry()
  - pgstat_fetch_entry()
  - pgstat_reset()
  - pgstat_reset_of_kind()
  - pgstat_have_entry()
  - pgstat_snapshot_fixed()
  - pgstat_init_entry()
  - pgstat_reset_entry()

Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when
the kind is not known or registered.

This affects any extension using the custom cumulative statistics API
introduced in PG18.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion:
---
 src/backend/utils/activity/pgstat.c       | 45 ++++++++++++++++++++---
 src/backend/utils/activity/pgstat_shmem.c | 13 ++++++-
 2 files changed, 52 insertions(+), 6 deletions(-)
 100.0% src/backend/utils/activity/

diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..5180201c4e8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* not needed atm, and doesn't make sense with the current signature */
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	/* reset the "single counter" */
 	pgstat_reset_entry(kind, dboid, objid, ts);
@@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind)
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	if (kind_info->fixed_amount)
 		kind_info->reset_all_cb(ts);
 	else
@@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free)
 	void	   *stats_data;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* should be called from backends */
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 	Assert(!kind_info->fixed_amount);
@@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 bool
 pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/* fixed-numbered stats always exist */
-	if (pgstat_get_kind_info(kind)->fixed_amount)
+	if (kind_info->fixed_amount)
 		return true;
 
 	return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL;
@@ -1104,8 +1126,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
 void
 pgstat_snapshot_fixed(PgStat_Kind kind)
 {
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	Assert(pgstat_is_kind_valid(kind));
-	Assert(pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(kind_info->fixed_amount);
 
 	if (force_stats_snapshot_clear)
 		pgstat_clear_snapshot();
@@ -1310,9 +1339,15 @@ PgStat_EntryRef *
 pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
 	/* need to be able to flush out */
-	Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL);
+	Assert(kind_info->flush_pending_cb != NULL);
 
 	if (unlikely(!pgStatPendingContext))
 	{
@@ -1327,7 +1362,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 
 	if (entry_ref->pending == NULL)
 	{
-		size_t		entrysize = pgstat_get_kind_info(kind)->pending_size;
+		size_t		entrysize = kind_info->pending_size;
 
 		Assert(entrysize != (size_t) -1);
 
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 5ea3f1973f9..4e6a556af93 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind,
 	PgStatShared_Common *shheader;
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
+
 	/*
 	 * Initialize refcount to 1, marking it as valid / not dropped. The entry
 	 * can't be freed before the initialization because it can't be found as
@@ -1127,8 +1132,14 @@ void
 pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
+	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+
+	if (unlikely(kind_info == NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("statistics kind %u is not known or registered", kind)));
 
-	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
+	Assert(!kind_info->fixed_amount);
 
 	entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
-- 
2.34.1


--jlSGGOUQ0kGWBHE+--





^ permalink  raw  reply  [nested|flat] 656+ messages in thread

* Prevent crash when calling pgstat functions with unregistered stats kind
@ 2026-07-01 07:19 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  2026-07-01 08:20 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Ewan Young <kdbase.hack@gmail.com>
  0 siblings, 1 reply; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-01 07:19 UTC (permalink / raw)
  To: pgsql-hackers@lists.postgresql.org; +Cc: Michael Paquier <michael@paquier.xyz>

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] 656+ 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 <bertranddrouvot.pg@gmail.com>
@ 2026-07-01 08:20 ` Ewan Young <kdbase.hack@gmail.com>
  2026-07-02 03:27   ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 1 reply; 656+ messages in thread

From: Ewan Young @ 2026-07-01 08:20 UTC (permalink / raw)
  To: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>; +Cc: pgsql-hackers@lists.postgresql.org; Michael Paquier <michael@paquier.xyz>

Hi Bertrand,

On Wed, Jul 1, 2026 at 3:20 PM Bertrand Drouvot
<bertranddrouvot.pg@gmail.com> 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] 656+ 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 <bertranddrouvot.pg@gmail.com>
  2026-07-01 08:20 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Ewan Young <kdbase.hack@gmail.com>
@ 2026-07-02 03:27   ` Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  2026-07-02 03:43     ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Michael Paquier <michael@paquier.xyz>
  0 siblings, 1 reply; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-02 03:27 UTC (permalink / raw)
  To: Ewan Young <kdbase.hack@gmail.com>; +Cc: pgsql-hackers@lists.postgresql.org; Michael Paquier <michael@paquier.xyz>

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] 656+ 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 <bertranddrouvot.pg@gmail.com>
  2026-07-01 08:20 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Ewan Young <kdbase.hack@gmail.com>
  2026-07-02 03:27   ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
@ 2026-07-02 03:43     ` Michael Paquier <michael@paquier.xyz>
  2026-07-02 04:06       ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 1 reply; 656+ messages in thread

From: Michael Paquier @ 2026-07-02 03:43 UTC (permalink / raw)
  To: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>; +Cc: Ewan Young <kdbase.hack@gmail.com>; pgsql-hackers@lists.postgresql.org

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, ../../akXebziFr_eQgQi8@paquier.xyz/2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 656+ 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 <bertranddrouvot.pg@gmail.com>
  2026-07-01 08:20 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Ewan Young <kdbase.hack@gmail.com>
  2026-07-02 03:27   ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  2026-07-02 03:43     ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Michael Paquier <michael@paquier.xyz>
@ 2026-07-02 04:06       ` Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  2026-07-02 04:10         ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Michael Paquier <michael@paquier.xyz>
  0 siblings, 1 reply; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-02 04:06 UTC (permalink / raw)
  To: Michael Paquier <michael@paquier.xyz>; +Cc: Ewan Young <kdbase.hack@gmail.com>; pgsql-hackers@lists.postgresql.org

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] 656+ 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 <bertranddrouvot.pg@gmail.com>
  2026-07-01 08:20 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Ewan Young <kdbase.hack@gmail.com>
  2026-07-02 03:27   ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  2026-07-02 03:43     ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Michael Paquier <michael@paquier.xyz>
  2026-07-02 04:06       ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
@ 2026-07-02 04:10         ` Michael Paquier <michael@paquier.xyz>
  2026-07-02 04:23           ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 1 reply; 656+ messages in thread

From: Michael Paquier @ 2026-07-02 04:10 UTC (permalink / raw)
  To: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>; +Cc: Ewan Young <kdbase.hack@gmail.com>; pgsql-hackers@lists.postgresql.org

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, ../../akXkqov6wLbKwpAd@paquier.xyz/2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 656+ 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 <bertranddrouvot.pg@gmail.com>
  2026-07-01 08:20 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Ewan Young <kdbase.hack@gmail.com>
  2026-07-02 03:27   ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  2026-07-02 03:43     ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Michael Paquier <michael@paquier.xyz>
  2026-07-02 04:06       ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  2026-07-02 04:10         ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Michael Paquier <michael@paquier.xyz>
@ 2026-07-02 04:23           ` Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  2026-07-02 04:43             ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 1 reply; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-02 04:23 UTC (permalink / raw)
  To: Michael Paquier <michael@paquier.xyz>; +Cc: Ewan Young <kdbase.hack@gmail.com>; pgsql-hackers@lists.postgresql.org

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] 656+ 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 <bertranddrouvot.pg@gmail.com>
  2026-07-01 08:20 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Ewan Young <kdbase.hack@gmail.com>
  2026-07-02 03:27   ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  2026-07-02 03:43     ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Michael Paquier <michael@paquier.xyz>
  2026-07-02 04:06       ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  2026-07-02 04:10         ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Michael Paquier <michael@paquier.xyz>
  2026-07-02 04:23           ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
@ 2026-07-02 04:43             ` Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  2026-07-02 05:00               ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Michael Paquier <michael@paquier.xyz>
  0 siblings, 1 reply; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-02 04:43 UTC (permalink / raw)
  To: Michael Paquier <michael@paquier.xyz>; +Cc: Ewan Young <kdbase.hack@gmail.com>; pgsql-hackers@lists.postgresql.org

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] 656+ 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 <bertranddrouvot.pg@gmail.com>
  2026-07-01 08:20 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Ewan Young <kdbase.hack@gmail.com>
  2026-07-02 03:27   ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  2026-07-02 03:43     ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Michael Paquier <michael@paquier.xyz>
  2026-07-02 04:06       ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  2026-07-02 04:10         ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Michael Paquier <michael@paquier.xyz>
  2026-07-02 04:23           ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  2026-07-02 04:43             ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
@ 2026-07-02 05:00               ` Michael Paquier <michael@paquier.xyz>
  2026-07-02 05:02                 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 1 reply; 656+ messages in thread

From: Michael Paquier @ 2026-07-02 05:00 UTC (permalink / raw)
  To: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>; +Cc: Ewan Young <kdbase.hack@gmail.com>; pgsql-hackers@lists.postgresql.org

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, ../../akXwVgoofSBd--pG@paquier.xyz/2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 656+ 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 <bertranddrouvot.pg@gmail.com>
  2026-07-01 08:20 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Ewan Young <kdbase.hack@gmail.com>
  2026-07-02 03:27   ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  2026-07-02 03:43     ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Michael Paquier <michael@paquier.xyz>
  2026-07-02 04:06       ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  2026-07-02 04:10         ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Michael Paquier <michael@paquier.xyz>
  2026-07-02 04:23           ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  2026-07-02 04:43             ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  2026-07-02 05:00               ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Michael Paquier <michael@paquier.xyz>
@ 2026-07-02 05:02                 ` Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  2026-07-02 06:53                   ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Michael Paquier <michael@paquier.xyz>
  0 siblings, 1 reply; 656+ messages in thread

From: Bertrand Drouvot @ 2026-07-02 05:02 UTC (permalink / raw)
  To: Michael Paquier <michael@paquier.xyz>; +Cc: Ewan Young <kdbase.hack@gmail.com>; pgsql-hackers@lists.postgresql.org

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] 656+ 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 <bertranddrouvot.pg@gmail.com>
  2026-07-01 08:20 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Ewan Young <kdbase.hack@gmail.com>
  2026-07-02 03:27   ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  2026-07-02 03:43     ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Michael Paquier <michael@paquier.xyz>
  2026-07-02 04:06       ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  2026-07-02 04:10         ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Michael Paquier <michael@paquier.xyz>
  2026-07-02 04:23           ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  2026-07-02 04:43             ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  2026-07-02 05:00               ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Michael Paquier <michael@paquier.xyz>
  2026-07-02 05:02                 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
@ 2026-07-02 06:53                   ` Michael Paquier <michael@paquier.xyz>
  0 siblings, 0 replies; 656+ messages in thread

From: Michael Paquier @ 2026-07-02 06:53 UTC (permalink / raw)
  To: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>; +Cc: Ewan Young <kdbase.hack@gmail.com>; pgsql-hackers@lists.postgresql.org

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, ../../akYK_kWLNx7UGXZe@paquier.xyz/2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 656+ messages in thread


end of thread, other threads:[~2026-07-02 06:53 UTC | newest]

Thread overview: 656+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2024-10-25 03:56 [PATCH v23 1/8] Row pattern recognition patch for raw parser. Tatsuo Ishii <ishii@postgresql.org>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 05:43 [PATCH v1] Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 07:19 Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-01 08:20 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Ewan Young <kdbase.hack@gmail.com>
2026-07-02 03:27   ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-02 03:43     ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Michael Paquier <michael@paquier.xyz>
2026-07-02 04:06       ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-02 04:10         ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Michael Paquier <michael@paquier.xyz>
2026-07-02 04:23           ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-02 04:43             ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-02 05:00               ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Michael Paquier <michael@paquier.xyz>
2026-07-02 05:02                 ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-07-02 06:53                   ` Re: Prevent crash when calling pgstat functions with unregistered stats kind Michael Paquier <michael@paquier.xyz>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox